# C Code Generation
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [1. Quick Start](#1-quick-start)
- [Generate header and implementation](#generate-header-and-implementation)
- [Compile and link](#compile-and-link)
- [2. Type Mapping](#2-type-mapping)
- [BIT STRING struct layout](#bit-string-struct-layout)
- [Named INTEGER values](#named-integer-values)
- [Constrained INTEGER](#constrained-integer)
- [OPTIONAL fields](#optional-fields)
- [Struct ordering](#struct-ordering)
- [Header includes](#header-includes)
- [3. Generated Function Signatures](#3-generated-function-signatures)
- [4. libcsynta API Used by Generated Code](#4-libcsynta-api-used-by-generated-code)
- [4.1 Decoder lifecycle](#41-decoder-lifecycle)
- [4.2 Decoder navigation](#42-decoder-navigation)
- [4.3 Primitive decode functions](#43-primitive-decode-functions)
- [4.4 String and time decode functions](#44-string-and-time-decode-functions)
- [4.5 Integer conversion and free](#45-integer-conversion-and-free)
- [4.6 OctetString accessors and free](#46-octetstring-accessors-and-free)
- [4.7 OID free](#47-oid-free)
- [4.8 Encoder lifecycle](#48-encoder-lifecycle)
- [4.9 Encoder navigation](#49-encoder-navigation)
- [4.10 Primitive encode functions](#410-primitive-encode-functions)
- [4.11 String and time encode functions](#411-string-and-time-encode-functions)
- [4.12 Tag and encoding types](#412-tag-and-encoding-types)
- [5. Memory Contract](#5-memory-contract)
- [Ownership rules](#ownership-rules)
- [Partial decode failure](#partial-decode-failure)
- [`_free()` contract](#_free-contract)
- [6. Error Handling](#6-error-handling)
- [SyntaErrorCode](#syntaerrorcode)
- [Error propagation](#error-propagation)
- [7. OPTIONAL Fields](#7-optional-fields)
- [Struct layout](#struct-layout)
- [Decode](#decode)
- [Encode](#encode)
- [8. Tagged Fields (IMPLICIT and EXPLICIT)](#8-tagged-fields-implicit-and-explicit)
- [IMPLICIT tags -- primitive fields](#implicit-tags-primitive-fields)
- [IMPLICIT tags -- structural types](#implicit-tags-structural-types)
- [EXPLICIT tags](#explicit-tags)
- [Current limitation](#current-limitation)
- [9. CHOICE Dispatch](#9-choice-dispatch)
- [Struct layout](#struct-layout-1)
- [Decode](#decode-1)
- [Encode](#encode-1)
- [10. Constraint Validation Options](#10-constraint-validation-options)
- [Default (no flags)](#default-no-flags)
- [--with-regex (POSIX regex)](#-with-regex-posix-regex)
- [--with-pcre (PCRE2)](#-with-pcre-pcre2)
- [--with-containing (CONTAINING constraint)](#-with-containing-containing-constraint)
- [11. Arena / Bump Allocator Mode](#11-arena-bump-allocator-mode)
- [Arena type and helpers (generated in header)](#arena-type-and-helpers-generated-in-header)
- [Additional generated function](#additional-generated-function)
- [Usage pattern](#usage-pattern)
- [12. Helper Functions (--with-helpers)](#12-helper-functions-with-helpers)
- [_init](#_init)
- [_validate](#_validate)
- [_print](#_print)
- [13. Build System Integration](#13-build-system-integration)
- [--cmake](#-cmake)
- [--meson](#-meson)
- [14. Multi-Module Projects](#14-multi-module-projects)
This document covers how synta-codegen generates C headers and implementation
files from ASN.1 schemas, and documents the exact libcsynta API that the
generated code depends on at link time.
---
## 1. Quick Start
### Generate header and implementation
```sh
synta-codegen --lang c schema.asn1
synta-codegen --lang c -o types.h schema.asn1
synta-codegen --lang c --impl types.h -o types.c schema.asn1
synta-codegen --lang c --header-path /usr/local/include/synta.h -o types.h schema.asn1
```
### Compile and link
The generated `.c` file depends on libcsynta. A minimal compile-and-link
sequence using pkg-config looks like:
```sh
cc -c types.c -o types.o $(pkg-config --cflags csynta)
cc myapp.c types.o -o myapp $(pkg-config --libs csynta)
```
Without pkg-config, pass the include and library directories manually:
```sh
cc -I/path/to/synta/include -c types.c -o types.o
cc myapp.c types.o -o myapp -L/path/to/synta/lib -lcsynta
```
The generated code requires a C99-capable compiler (`-std=c99` or later).
---
## 2. Type Mapping
The table below shows how each ASN.1 type maps to a C type in the generated
header.
| ASN.1 type | C type | Notes |
|-----------------------------------------|-------------------------------|--------------------------------------------|
| INTEGER | `int64_t` | `stdint.h`; see Named Numbers below |
| INTEGER (with named numbers) | `enum TypeNameValue` | enum constants generated alongside struct |
| INTEGER (constrained) | `typedef struct { uintN_t value; }` | smallest `uintN_t`/`intN_t`; see Constrained INTEGER below |
| BOOLEAN | `bool` | `stdbool.h` |
| OCTET STRING | `SyntaOctetString*` | heap-allocated |
| BIT STRING | `SyntaBitString` | struct by value; see below |
| OBJECT IDENTIFIER | `SyntaObjectIdentifier*` | heap-allocated |
| NULL | (no field) | skipped entirely in struct |
| REAL | `double` | |
| UTF8String | `SyntaOctetString*` | raw bytes, heap-allocated |
| PrintableString | `SyntaOctetString*` | raw bytes, heap-allocated |
| IA5String | `SyntaOctetString*` | raw bytes, heap-allocated |
| UTCTime | `SyntaOctetString*` | raw bytes, heap-allocated |
| GeneralizedTime | `SyntaOctetString*` | raw bytes, heap-allocated |
| SEQUENCE | `struct TypeName { ... }` | |
| SET | `struct TypeName { ... }` | |
| SEQUENCE OF ElementType | `struct TypeName { ElementType *items; size_t count; }` | |
| SET OF ElementType | `struct TypeName { ElementType *items; size_t count; }` | |
| CHOICE | `struct TypeName { enum TypeNameTag tag; union { ... } value; }` | |
| TypeRef | referenced type by value | |
| ANY / ANY DEFINED BY | `SyntaOctetString*` | heap-allocated raw DER bytes |
### BIT STRING struct layout
`SyntaBitString` is defined in `synta.h` as:
```c
typedef struct {
uint8_t *data;
size_t len;
uint8_t unused_bits;
} SyntaBitString;
```
`data` points into libcsynta-managed memory. Do not free `data` directly; call
the relevant `_free()` function for the enclosing struct.
### Named INTEGER values
When an INTEGER type carries named numbers, synta-codegen generates an enum or
a set of `#define` constants alongside the struct:
```c
typedef enum {
Version_v1 = 0,
Version_v2 = 1,
Version_v3 = 2,
} VersionValue;
```
The struct field type is still `int64_t`; the enum serves as a companion
constant set.
### Constrained INTEGER
When an INTEGER type carries a value-range constraint (e.g. `INTEGER (0..100)`),
synta-codegen generates a `typedef struct` newtype whose `value` field uses the
smallest C integer type that covers the declared range:
- Lower bound ≥ 0 → unsigned: `uint8_t` (≤255), `uint16_t` (≤65535),
`uint32_t` (≤4294967295), `uint64_t`.
- Lower bound < 0 → signed: `int8_t`, `int16_t`, `int32_t`, `int64_t`.
- Unconstrained bounds (`MIN`/`MAX`, named values) → `int64_t`.
For example, `Percentage ::= INTEGER (0..100)` produces:
```c
typedef struct { uint8_t value; } Percentage;
static inline bool percentage_new(uint8_t v, Percentage* out) {
if (!(v <= 100LL)) return false;
out->value = v;
return true;
}
static inline Percentage percentage_new_unchecked(uint8_t v) {
Percentage out; out.value = v; return out;
}
static inline uint8_t percentage_get(const Percentage* self) { return self->value; }
```
Note that for unsigned storage types the lower-bound check (e.g. `v >= 0`) is
omitted from the generated validation expression because it is always true and
would trigger compiler warnings.
### OPTIONAL fields
Each OPTIONAL member `fieldname` in a SEQUENCE or SET gains a companion
boolean immediately before it:
```c
struct MySequence {
bool has_optional_field;
SyntaOctetString *optional_field;
};
```
See Section 7 for decode and encode patterns.
### Struct ordering
All generated struct types appear in topological order: leaf types (those
referenced by others) are emitted first. Every struct type also receives a
forward declaration before the definitions block, so pointer types are always
valid.
### Header includes
Every generated header begins with:
```c
#include <stdint.h>
#include <stdbool.h>
#include "synta.h"
```
---
## 3. Generated Function Signatures
For each type `TypeName` the code generator emits at minimum three functions:
```c
SyntaErrorCode TypeName_decode(SyntaDecoder *decoder, TypeName *out);
SyntaErrorCode TypeName_encode(SyntaEncoder *encoder, const TypeName *value);
void TypeName_free(TypeName *value);
```
With `--with-helpers` three additional static inline helpers appear in the
header (see Section 12).
With `--arena` an additional decode variant is generated (see Section 11).
---
## 4. libcsynta API Used by Generated Code
The following functions are called directly by generated decode, encode, and
free implementations. Any program that links against generated code must also
link against libcsynta, which provides all of these symbols.
Functions are grouped by category.
### 4.1 Decoder lifecycle
```c
SyntaDecoder *synta_decoder_new(const uint8_t *data, size_t len,
SyntaEncoding encoding);
void synta_decoder_free(SyntaDecoder *decoder);
```
### 4.2 Decoder navigation
```c
SyntaErrorCode synta_decoder_enter_sequence(SyntaDecoder *parent,
SyntaDecoder **out);
SyntaErrorCode synta_decoder_enter_set(SyntaDecoder *parent,
SyntaDecoder **out);
SyntaErrorCode synta_decoder_enter_constructed(SyntaDecoder *parent,
SyntaTag tag,
SyntaDecoder **out);
bool synta_decoder_at_end(SyntaDecoder *decoder);
SyntaErrorCode synta_decoder_peek_tag(SyntaDecoder *decoder, SyntaTag *out);
```
### 4.3 Primitive decode functions
```c
enum SyntaErrorCode synta_decode_integer(SyntaDecoder *decoder,
SyntaInteger **out);
enum SyntaErrorCode synta_decode_boolean(SyntaDecoder *decoder, bool *out);
enum SyntaErrorCode synta_decode_octet_string(SyntaDecoder *decoder,
SyntaOctetString **out);
enum SyntaErrorCode synta_decode_bit_string(SyntaDecoder *decoder,
SyntaByteArray *out,
uint8_t *bits_unused);
enum SyntaErrorCode synta_decode_oid(SyntaDecoder *decoder,
SyntaObjectIdentifier **out);
enum SyntaErrorCode synta_decode_null(SyntaDecoder *decoder);
enum SyntaErrorCode synta_decode_real(SyntaDecoder *decoder, double *out);
```
### 4.4 String and time decode functions
Each function allocates a heap `SyntaOctetString*` containing the raw bytes of
the string or time value.
```c
SyntaErrorCode synta_decode_utf8_string_os(SyntaDecoder *decoder,
SyntaOctetString **out);
SyntaErrorCode synta_decode_printable_string_os(SyntaDecoder *decoder,
SyntaOctetString **out);
SyntaErrorCode synta_decode_ia5_string_os(SyntaDecoder *decoder,
SyntaOctetString **out);
SyntaErrorCode synta_decode_utctime_bytes(SyntaDecoder *decoder,
SyntaOctetString **out);
SyntaErrorCode synta_decode_generalizedtime_bytes(SyntaDecoder *decoder,
SyntaOctetString **out);
```
### 4.5 Integer conversion and free
```c
SyntaErrorCode synta_integer_to_i64(SyntaInteger *integer, int64_t *out);
void synta_integer_free(SyntaInteger *integer);
SyntaInteger *synta_integer_new_i64(int64_t value);
```
### 4.6 OctetString accessors and free
```c
const uint8_t *synta_octet_string_data(const SyntaOctetString *os);
size_t synta_octet_string_len(const SyntaOctetString *os);
void synta_octet_string_free(SyntaOctetString *os);
```
### 4.7 OID free
```c
void synta_oid_free(SyntaObjectIdentifier *oid);
```
### 4.8 Encoder lifecycle
```c
SyntaEncoder *synta_encoder_new(SyntaEncoding encoding);
void synta_encoder_free(SyntaEncoder *encoder);
const uint8_t *synta_encoder_get_bytes(SyntaEncoder *encoder, size_t *len);
```
### 4.9 Encoder navigation
```c
SyntaErrorCode synta_encoder_start_sequence(SyntaEncoder *parent,
SyntaEncoder **out);
SyntaErrorCode synta_encoder_start_set(SyntaEncoder *parent,
SyntaEncoder **out);
SyntaErrorCode synta_encoder_start_constructed(SyntaEncoder *parent,
SyntaTag tag,
SyntaEncoder **out);
SyntaErrorCode synta_encoder_end_constructed(SyntaEncoder *encoder);
```
### 4.10 Primitive encode functions
```c
SyntaErrorCode synta_encode_integer(SyntaEncoder *encoder,
const SyntaInteger *integer);
SyntaErrorCode synta_encode_boolean(SyntaEncoder *encoder, bool value);
SyntaErrorCode synta_encode_octet_string(SyntaEncoder *encoder,
const uint8_t *data,
size_t len);
SyntaErrorCode synta_encode_bit_string(SyntaEncoder *encoder,
const uint8_t *data,
size_t len,
uint8_t unused_bits);
SyntaErrorCode synta_encode_object_identifier(SyntaEncoder *encoder,
const SyntaObjectIdentifier *oid);
SyntaErrorCode synta_encode_null(SyntaEncoder *encoder);
SyntaErrorCode synta_encode_real(SyntaEncoder *encoder, double value);
```
### 4.11 String and time encode functions
```c
SyntaErrorCode synta_encode_utf8_string_bytes(SyntaEncoder *encoder,
const uint8_t *data,
size_t len);
SyntaErrorCode synta_encode_printable_string_bytes(SyntaEncoder *encoder,
const uint8_t *data,
size_t len);
SyntaErrorCode synta_encode_ia5_string_bytes(SyntaEncoder *encoder,
const uint8_t *data,
size_t len);
```
Additional string and time encode variants (UTCTime, GeneralizedTime) follow
the same signature pattern.
### 4.12 Tag and encoding types
```c
typedef enum SyntaTagClass {
SyntaTagClass_Universal = 0,
SyntaTagClass_Application = 1,
SyntaTagClass_ContextSpecific = 2,
SyntaTagClass_Private = 3,
} SyntaTagClass;
typedef struct SyntaTag {
SyntaTagClass class_;
bool constructed;
uint32_t number;
} SyntaTag;
typedef enum SyntaEncoding {
SyntaEncoding_Der = 0,
SyntaEncoding_Ber = 1,
} SyntaEncoding;
```
---
## 5. Memory Contract
### Ownership rules
- `_decode()` allocates heap memory for `SyntaOctetString*`,
`SyntaInteger*`, and `SyntaObjectIdentifier*` fields. The caller owns
those allocations through the struct.
- `SyntaBitString` is decoded into a caller-provided struct; its `data`
pointer points into libcsynta-managed memory and must not be freed
directly.
- SEQUENCE OF and SET OF arrays (`items` pointer) are allocated by
`_decode()` with `malloc`. The caller owns this allocation.
- The struct itself is always caller-owned. `_decode()` expects a pointer
to an already-allocated (or stack-allocated) struct and fills it in.
### Partial decode failure
If `_decode()` returns a non-success error code partway through a SEQUENCE,
fields that were successfully decoded before the failure are left in the
struct. They are NOT automatically freed. The caller must call `_free()` on
the partially-filled struct to release any already-allocated fields.
Recommended pattern:
```c
MyType result = {0};
SyntaErrorCode err = MyType_decode(decoder, &result);
if (err != SyntaErrorCode_Success) {
MyType_free(&result);
return err;
}
MyType_free(&result);
```
### `_free()` contract
- `_free()` is safe to call on a zero-initialized struct. It checks each
pointer field for NULL before freeing.
- `_free()` does NOT free the struct itself. The caller is responsible for
the struct's storage (stack, heap, or embedded in a larger struct).
- For SEQUENCE OF / SET OF: if `items` is non-NULL, `_free()` frees each
element within the array (calling the element type's own free logic), then
calls `free(items)` and sets `count` to 0.
- Calling `_free()` more than once on the same struct is undefined behavior.
Zero the struct after freeing if re-use is required.
---
## 6. Error Handling
### SyntaErrorCode
All decode and encode functions return `SyntaErrorCode`. The relevant values
are:
```c
typedef enum SyntaErrorCode {
SyntaErrorCode_Success = 0,
SyntaErrorCode_InvalidTag = 1,
SyntaErrorCode_InvalidLength = 2,
SyntaErrorCode_UnexpectedEof = 3,
SyntaErrorCode_InvalidEncoding = 4,
} SyntaErrorCode;
```
### Error propagation
Generated decode functions propagate errors from every libcsynta call
immediately, using a pattern equivalent to:
```c
SyntaErrorCode err;
err = synta_decoder_enter_sequence(decoder, &seq);
if (err != SyntaErrorCode_Success) return err;
err = FieldType_decode(seq, &out->field);
if (err != SyntaErrorCode_Success) {
synta_decoder_free(seq);
return err;
}
```
Because partial allocations are not freed automatically (see Section 5), the
caller must check every return value and call `_free()` on the output struct
before propagating a failure.
---
## 7. OPTIONAL Fields
### Struct layout
For each OPTIONAL field `fieldname` of type `FieldType`, the generated struct
contains:
```c
bool has_fieldname;
FieldType fieldname;
```
The `has_fieldname` bool appears immediately before its associated field.
### Decode
The generated decode implementation uses `synta_decoder_peek_tag()` to inspect
the next tag. If the tag matches the expected tag for `fieldname`, the field is
decoded and `has_fieldname` is set to `true`. If it does not match, the field
is left at its zero-initialized value and `has_fieldname` remains `false`.
```c
SyntaTag peeked;
err = synta_decoder_peek_tag(seq, &peeked);
if (err == SyntaErrorCode_Success &&
peeked.class_ == SyntaTagClass_ContextSpecific &&
peeked.number == 0) {
err = FieldType_decode(seq, &out->fieldname);
if (err != SyntaErrorCode_Success) return err;
out->has_fieldname = true;
}
```
### Encode
The generated encode implementation checks `has_fieldname` before encoding:
```c
if (value->has_fieldname) {
err = FieldType_encode(encoder, &value->fieldname);
if (err != SyntaErrorCode_Success) return err;
}
```
---
## 8. Tagged Fields (IMPLICIT and EXPLICIT)
### IMPLICIT tags -- primitive fields
For a primitive field with an IMPLICIT context tag, the generated code calls
the primitive decode/encode function directly. The tag on the wire is replaced
by the context tag; no separate container is created.
### IMPLICIT tags -- structural types
For fields whose underlying type is a SEQUENCE, SET, SEQUENCE OF, SET OF, or a
TypeRef that resolves to one of these, the generated code uses
`synta_decoder_enter_constructed()` (or `synta_encoder_start_constructed()`)
to enter the tagged wrapper, then decodes or encodes the inner fields directly:
```c
SyntaTag impl_tag = { SyntaTagClass_ContextSpecific, true, 0 };
SyntaDecoder *inner;
err = synta_decoder_enter_constructed(parent, impl_tag, &inner);
if (err != SyntaErrorCode_Success) return err;
synta_decoder_free(inner);
```
```c
SyntaTag impl_tag = { SyntaTagClass_ContextSpecific, true, 0 };
SyntaEncoder *inner;
err = synta_encoder_start_constructed(parent, impl_tag, &inner);
if (err != SyntaErrorCode_Success) return err;
err = synta_encoder_end_constructed(inner);
```
### EXPLICIT tags
For both primitive and TypeRef inner types, the generated code wraps the inner
decode or encode call in an outer constructed element carrying the explicit tag:
```c
SyntaTag expl_tag = { SyntaTagClass_ContextSpecific, true, 2 };
SyntaDecoder *tagged;
err = synta_decoder_enter_constructed(parent, expl_tag, &tagged);
if (err != SyntaErrorCode_Success) return err;
err = SomeType_decode(tagged, &out->field);
synta_decoder_free(tagged);
if (err != SyntaErrorCode_Success) return err;
```
```c
SyntaTag expl_tag = { SyntaTagClass_ContextSpecific, true, 2 };
SyntaEncoder *tagged;
err = synta_encoder_start_constructed(parent, expl_tag, &tagged);
if (err != SyntaErrorCode_Success) return err;
err = SomeType_encode(tagged, &value->field);
if (err != SyntaErrorCode_Success) return err;
err = synta_encoder_end_constructed(tagged);
```
### Current limitation
Anonymous inline SEQUENCE, SET, or CHOICE types nested directly inside an
EXPLICIT tag -- without going through a named TypeRef -- are not supported by
the code generator. Define a named type and reference it instead.
---
## 9. CHOICE Dispatch
### Struct layout
A CHOICE type generates:
```c
typedef enum {
TypeName_TAG_Variant1,
TypeName_TAG_Variant2,
} TypeNameTag;
typedef struct {
TypeNameTag tag;
union {
Variant1Type variant1;
Variant2Type variant2;
} value;
} TypeName;
```
### Decode
The generated decode function calls `synta_decoder_peek_tag()` on the decoder
(without entering any container) and dispatches to the variant's decode
function based on the tag class and number:
```c
SyntaTag peeked;
err = synta_decoder_peek_tag(decoder, &peeked);
if (err != SyntaErrorCode_Success) return err;
if (peeked.class_ == SyntaTagClass_Universal && peeked.number == 16) {
out->tag = TypeName_TAG_Variant1;
return Variant1Type_decode(decoder, &out->value.variant1);
} else if (...) {
...
}
return SyntaErrorCode_InvalidTag;
```
### Encode
The generated encode function switches on `value->tag`:
```c
switch (value->tag) {
case TypeName_TAG_Variant1:
return Variant1Type_encode(encoder, &value->value.variant1);
case TypeName_TAG_Variant2:
return Variant2Type_encode(encoder, &value->value.variant2);
default:
return SyntaErrorCode_InvalidTag;
}
```
---
## 10. Constraint Validation Options
synta-codegen can optionally generate runtime constraint checks in the `.c`
implementation file.
### Default (no flags)
Value range constraints and size constraints are emitted as comments only. No
runtime validation code is generated. PATTERN and CONTAINING constraints are
also comment-only.
### --with-regex (POSIX regex)
For fields with a PATTERN constraint, the generated code uses POSIX
`regcomp()`/`regexec()` (from `<regex.h>`):
- The pattern is compiled once (static variable, compiled on first call).
- Each decode call runs `regexec()` against the decoded string bytes.
- A failed match returns an error.
Link requirement: on most platforms `<regex.h>` is part of libc. No extra
library is needed.
### --with-pcre (PCRE2)
For PATTERN constraints, uses PCRE2 (`<pcre2.h>`):
- The pattern is compiled once with `pcre2_compile()`.
- Each decode call runs `pcre2_match()`.
Link requirement: `-lpcre2-8` (or the appropriate PCRE2 variant).
### --with-containing (CONTAINING constraint)
For fields carrying a CONTAINING constraint (indicating that the raw bytes must
be valid encodings of another type), the generated code allocates a scratch
decoder, decodes the inner type, and frees it:
```c
SyntaDecoder *scratch = synta_decoder_new(
synta_octet_string_data(out->field),
synta_octet_string_len(out->field),
SyntaEncoding_Der);
if (!scratch) return SyntaErrorCode_OutOfMemory;
InnerType tmp = {0};
err = InnerType_decode(scratch, &tmp);
InnerType_free(&tmp);
synta_decoder_free(scratch);
if (err != SyntaErrorCode_Success) return err;
```
Without `--with-containing` the check is a comment only.
---
## 11. Arena / Bump Allocator Mode
With `--arena`, the generated header includes an arena type and three inline
helpers, and each type gains an additional `_decode_arena()` function.
### Arena type and helpers (generated in header)
```c
typedef struct {
void *_ptrs[SYNTA_ARENA_CAPACITY];
void (*_fns[SYNTA_ARENA_CAPACITY])(void *);
size_t _n;
} SyntaArena;
static inline void synta_arena_init(SyntaArena *arena);
static inline void _synta_arena_track(SyntaArena *arena,
void *ptr,
void (*fn)(void *));
static inline void synta_arena_free_all(SyntaArena *arena);
```
### Additional generated function
```c
SyntaErrorCode TypeName_decode_arena(SyntaDecoder *decoder,
SyntaArena *arena,
TypeName *out);
```
`_decode_arena()` behaves like `_decode()` but registers every heap allocation
(OctetString, Integer, OID, items arrays) with the arena instead of leaving
them for the caller to track individually.
### Usage pattern
```c
SyntaArena arena;
synta_arena_init(&arena);
TypeName result = {0};
SyntaErrorCode err = TypeName_decode_arena(decoder, &arena, &result);
if (err != SyntaErrorCode_Success) {
synta_arena_free_all(&arena);
return err;
}
synta_arena_free_all(&arena);
```
There is no need to call `TypeName_free()` when using arena mode; `synta_arena_free_all()`
releases everything tracked by `_decode_arena()`.
---
## 12. Helper Functions (--with-helpers)
With `--with-helpers`, three additional static inline functions are generated
in the header for each type.
### _init
Zero-initializes every field in the struct to 0, NULL, or false. Equivalent to
`memset(out, 0, sizeof(*out))` but expressed field-by-field for clarity.
```c
static inline void TypeName_init(TypeName *out);
```
### _validate
Checks that all required pointer fields are non-NULL, and that a CHOICE struct
has a recognized tag value. Returns `SyntaErrorCode_Success` or
`SyntaErrorCode_NullPointer` / `SyntaErrorCode_InvalidTag`.
```c
static inline SyntaErrorCode TypeName_validate(const TypeName *value);
```
### _print
Writes a human-readable debug representation to a `FILE*` stream. Handles NULL
input gracefully (prints `"(null)"`). Intended for debugging, not for
production output.
```c
static inline void TypeName_print(const TypeName *value, FILE *stream);
```
`_print` requires `<stdio.h>` to be included before the generated header, or
the generated header includes it automatically when `--with-helpers` is active.
---
## 13. Build System Integration
### --cmake
Generates a `CMakeLists.txt` targeting CMake 3.10 or later with C99 standard.
Key properties of the generated file:
- Defines a `Synta::Synta` IMPORTED target that wraps libcsynta. The path is
taken from `--synta-root` if provided, or from a `SYNTA_ROOT` CMake cache
variable at configure time.
- Generates one `add_library()` call per schema module, in topological order.
- Sets platform-specific link libraries automatically:
- Linux / BSD: `pthread`, `dl`, `m`
- Apple: `pthread`
- Windows: `ws2_32`, `userenv`, `bcrypt`
- Default library type is STATIC. Pass `--shared` to switch to SHARED.
Example generated CMakeLists excerpt:
```cmake
cmake_minimum_required(VERSION 3.10)
project(MySchema C)
set(CMAKE_C_STANDARD 99)
# ... Synta::Synta imported target setup ...
add_library(my_schema STATIC types.c)
target_link_libraries(my_schema PUBLIC Synta::Synta)
```
### --meson
Generates a `meson.build` file with `c_std=c99` and `warning_level=2`.
Key properties:
- If `--synta-root` is provided, locates libcsynta with
`cc.find_library('csynta', dirs: synta_root)`.
- Otherwise, uses `dependency('synta')` to locate it via pkg-config or the
Meson wrap system.
- Generates one `library()` or `shared_library()` call per module.
- Generates `declare_dependency()` entries for inter-module dependencies.
---
## 14. Multi-Module Projects
For schemas that import types from other schemas, pass `--output-dir` with
`--emit both` to generate all modules into a single directory at once:
```sh
synta-codegen --lang c --emit both --output-dir ./generated/ \
base.asn1 extension.asn1
```
Each module produces a `<stem>.h` and `<stem>.c` pair in topological order.
Import references resolve to the corresponding module header.
Use `--check-imports` to validate that all inter-module imports resolve
correctly without generating any output:
```sh
synta-codegen --check-imports base.asn1 extension.asn1
```
When combining `--cmake` or `--meson` with `--output-dir`, the build file and
all C sources (headers and implementations) are generated together in a single
invocation, covering all modules in dependency order:
```sh
synta-codegen --cmake --output-dir ./generated/ base.asn1 extension.asn1
synta-codegen --meson --output-dir ./generated/ base.asn1 extension.asn1
```