API Reference

This is a practical guide to every public function in TriePack. For auto-generated docs with parameter types, build with BUILD_DOCS=ON.

Use the tabs below to switch between C, C++, Python, and JavaScript APIs.


Error Handling

Every C function returns tp_result. Check for TP_OK (0) on success. Use tp_result_str() to get a human-readable error message:

tp_result rc = tp_dict_open(&dict, buf, len);
if (rc != TP_OK) {
    fprintf(stderr, "open failed: %s\n", tp_result_str(rc));
    return 1;
}
Code Value Meaning
TP_OK 0 Success
TP_ERR_EOF -1 Read past end of stream
TP_ERR_ALLOC -2 Memory allocation failed
TP_ERR_INVALID_PARAM -3 NULL pointer or invalid argument
TP_ERR_INVALID_POSITION -4 Seek beyond bounds
TP_ERR_NOT_ALIGNED -5 Byte operation on non-aligned cursor
TP_ERR_OVERFLOW -6 VarInt exceeds max groups
TP_ERR_INVALID_UTF8 -7 Malformed UTF-8
TP_ERR_BAD_MAGIC -10 Not a .trp file
TP_ERR_VERSION -11 Unsupported format version
TP_ERR_CORRUPT -12 Integrity check failed
TP_ERR_NOT_FOUND -13 Key not in dictionary
TP_ERR_TRUNCATED -14 Data shorter than header claims
TP_ERR_JSON_SYNTAX -20 Malformed JSON
TP_ERR_JSON_DEPTH -21 Nesting exceeds limit
TP_ERR_JSON_TYPE -22 Type mismatch on path lookup

The C++ wrappers use the same C error codes (tp_result). The RAII wrappers throw no exceptions – check return values the same way:

#include <triepack/triepack.hpp>

triepack::Dict dict(data, size);
// Constructor validates the buffer; check dict.handle() != nullptr

See the C tab for the full error code table.

The Python binding raises standard exceptions on error:

import triepack

try:
    data = triepack.decode(buf)
except ValueError as e:
    print(f"decode failed: {e}")  # corrupt, bad magic, etc.
except TypeError as e:
    print(f"bad argument: {e}")   # wrong type passed
C Error Code Python Exception
TP_ERR_BAD_MAGIC ValueError
TP_ERR_CORRUPT ValueError
TP_ERR_TRUNCATED ValueError
TP_ERR_INVALID_PARAM TypeError

The JavaScript binding throws Error on failure:

const triepack = require('triepack');

try {
    const data = triepack.decode(buf);
} catch (e) {
    console.error('decode failed:', e.message);
}
C Error Code JS Exception
TP_ERR_BAD_MAGIC Error
TP_ERR_CORRUPT Error
TP_ERR_TRUNCATED Error
TP_ERR_INVALID_PARAM TypeError

Values

Value Types (triepack/triepack_common.h)

TriePack values are tagged unions. The type field tells you which union member to read:

tp_value val;
tp_dict_lookup(dict, "key", &val);

switch (val.type) {
case TP_NULL:    /* no data */                              break;
case TP_BOOL:    printf("%s", val.data.bool_val ? "true" : "false"); break;
case TP_INT:     printf("%lld", (long long)val.data.int_val);        break;
case TP_UINT:    printf("%llu", (unsigned long long)val.data.uint_val); break;
case TP_FLOAT32: printf("%f", (double)val.data.float32_val);         break;
case TP_FLOAT64: printf("%f", val.data.float64_val);                 break;
case TP_STRING:  printf("%.*s", (int)val.data.string_val.str_len,
                                val.data.string_val.str);            break;
case TP_BLOB:    /* val.data.blob_val.data, val.data.blob_val.len */ break;
default: break;
}

Construction Helpers

Use these to build tp_value structs for insertion into an encoder:

tp_value tp_value_null(void);

Creates a null value. Useful as a sentinel or “key exists with no data.”

tp_value tp_value_bool(bool val);

Creates a boolean value. Encoded as a single bit (1 = true, 0 = false).

tp_value tp_value_int(int64_t val);

Creates a signed 64-bit integer. Encoded as a zigzag VarInt – small magnitudes (positive or negative) use fewer bytes.

tp_value tp_value_uint(uint64_t val);

Creates an unsigned 64-bit integer. Encoded as an unsigned VarInt.

tp_value tp_value_float32(float val);

Creates a 32-bit IEEE 754 float. Always takes 4 bytes in the encoded output.

tp_value tp_value_float64(double val);

Creates a 64-bit IEEE 754 double. Always takes 8 bytes in the encoded output.

tp_value tp_value_string(const char *str);

Creates a string value from a NUL-terminated C string. The string is copied into the encoded output. On decode, the string pointer points directly into the .trp buffer (zero-copy).

tp_value tp_value_string_n(const char *str, size_t len);

Creates a string value with explicit length. Can contain embedded NUL bytes.

tp_value tp_value_blob(const uint8_t *data, size_t len);

Creates a binary blob value. Like string but for arbitrary bytes.

Example – inserting values into an encoder:

tp_value v;

v = tp_value_int(42);
tp_encoder_add(enc, "count", &v);

v = tp_value_string("hello world");
tp_encoder_add(enc, "greeting", &v);

v = tp_value_bool(true);
tp_encoder_add(enc, "active", &v);

/* Key-only entry (set membership, no value) */
tp_encoder_add(enc, "flag", NULL);

Value Types

The C++ wrappers use the same tp_value struct from the C API. See the C tab for the full type list and construction helpers.

#include <triepack/triepack.hpp>

triepack::Encoder enc;
enc.insert("count", 42);
enc.insert("greeting", "hello world");

The insert() method accepts native C++ types and constructs the appropriate tp_value internally.

Value Types

Python values map to native types automatically:

TriePack Type Python Type
TP_NULL None
TP_BOOL bool
TP_INT int
TP_UINT int
TP_FLOAT32 float
TP_FLOAT64 float
TP_STRING str
TP_BLOB bytes
import triepack

data = {
    "count": 42,
    "greeting": "hello world",
    "active": True,
    "pi": 3.14159,
    "nothing": None,
}

buf = triepack.encode(data)
result = triepack.decode(buf)
# result == data

No manual value construction needed – pass a Python dict directly.

Value Types

JavaScript values map to native types automatically:

TriePack Type JS Type
TP_NULL null
TP_BOOL boolean
TP_INT number
TP_UINT number
TP_FLOAT32 number
TP_FLOAT64 number
TP_STRING string
TP_BLOB Uint8Array
const triepack = require('triepack');

const data = {
    count: 42,
    greeting: "hello world",
    active: true,
    pi: 3.14159,
    nothing: null,
};

const buf = triepack.encode(data);
const result = triepack.decode(buf);
// result deep-equals data

No manual value construction needed – pass a plain object directly.


Encoder

The encoder collects key-value pairs and builds a compressed .trp blob.

Create an Encoder

tp_result tp_encoder_create(tp_encoder **out);

Allocates a new encoder with default options. Pass a pointer to your tp_encoder * variable. Returns TP_ERR_ALLOC if allocation fails.

tp_encoder *enc = NULL;
tp_result rc = tp_encoder_create(&enc);

For custom options (addressing mode, checksum type, manual bits-per-symbol):

tp_result tp_encoder_create_ex(tp_encoder **out, const tp_encoder_options *opts);
tp_encoder_options opts = tp_encoder_defaults();
opts.bits_per_symbol = 6;  /* force 6-bit symbols */

tp_encoder *enc = NULL;
tp_encoder_create_ex(&enc, &opts);

Encoder Options

tp_encoder_options tp_encoder_defaults(void);

Returns the default configuration:

  • trie_mode = TP_ADDR_BIT (bit-level symbol packing)
  • value_mode = TP_ADDR_BYTE (byte-aligned values)
  • checksum = TP_CHECKSUM_CRC32
  • bits_per_symbol = 0 (auto-detect from key alphabet)

Add Entries

tp_result tp_encoder_add(tp_encoder *enc, const char *key, const tp_value *val);

Adds a NUL-terminated key with an optional value. Pass NULL for val to add a key-only entry (set membership).

tp_result tp_encoder_add_n(tp_encoder *enc, const char *key, size_t key_len,
                            const tp_value *val);

Adds a key with explicit length. Use when keys may contain NUL bytes.

uint32_t tp_encoder_count(const tp_encoder *enc);

Returns how many entries have been added.

Duplicate keys: If you add the same key twice, the last value wins.

Build the Trie

tp_result tp_encoder_build(tp_encoder *enc, uint8_t **buf, size_t *len);

Sorts entries, builds the compressed trie, and returns a heap-allocated buffer. The caller must free() the buffer when done.

uint8_t *buf = NULL;
size_t len = 0;
tp_result rc = tp_encoder_build(enc, &buf, &len);
if (rc == TP_OK) {
    /* buf contains len bytes of .trp data */
    /* ... use buf ... */
    free(buf);
}

Reset and Destroy

tp_result tp_encoder_reset(tp_encoder *enc);

Clears all entries but keeps the encoder options. Reuse the encoder without re-allocating.

tp_result tp_encoder_destroy(tp_encoder **enc);

Frees the encoder and sets *enc = NULL.

triepack::Encoder

RAII wrapper around tp_encoder. Include <triepack/triepack.hpp>.

#include <triepack/triepack.hpp>

triepack::Encoder enc;
enc.insert("apple", 42);
enc.insert("banana", 17);

const uint8_t *data;
size_t size;
int rc = enc.encode(&data, &size);
// data points to the encoded .trp blob
// data is valid until enc is destroyed or encode() is called again
Method Description
Encoder() Construct with default options
void insert(const char *key, int32_t value) Add a key-value pair
int encode(const uint8_t **data, size_t *size) Build the trie, receive pointer to blob
tp_encoder *handle() Access the underlying C handle

Move semantics are supported:

triepack::Encoder enc1;
enc1.insert("key", 1);

triepack::Encoder enc2 = std::move(enc1);
// enc1 is now empty (handle() == nullptr)

triepack.encode(data)

The Python encoder is a single function call. Pass a dict with string keys and supported value types:

import triepack

data = {
    "apple": 42,
    "banana": 17,
    "cherry": "red",
    "active": True,
}

buf = triepack.encode(data)
# buf is a bytes object containing the .trp data

Parameters:

  • data (dict): String keys mapping to None, bool, int, float, str, or bytes.

Returns: bytes – the encoded .trp blob.

Raises: TypeError if keys are not strings or values have unsupported types.

triepack.encode(data)

The JavaScript encoder is a single function call. Pass a plain object:

const triepack = require('triepack');

const data = {
    apple: 42,
    banana: 17,
    cherry: "red",
    active: true,
};

const buf = triepack.encode(data);
// buf is a Uint8Array containing the .trp data

Parameters:

  • data (Object): String keys mapping to null, boolean, number, string, or Uint8Array.

Returns: Uint8Array – the encoded .trp blob.

Throws: TypeError if keys are not strings or values have unsupported types.


Dictionary (Decoder)

The dictionary reader provides read-only access to a compiled .trp blob.

Open a Dictionary

tp_result tp_dict_open(tp_dict **out, const uint8_t *buf, size_t len);

Opens a .trp buffer for reading. Validates magic bytes, format version, and CRC-32 checksum. Returns TP_ERR_CORRUPT if the checksum fails.

The caller must keep buf alive for the lifetime of the dictionary – the dict reads directly from it (zero-copy).

tp_dict *dict = NULL;
tp_result rc = tp_dict_open(&dict, buf, len);
if (rc != TP_OK) {
    fprintf(stderr, "open: %s\n", tp_result_str(rc));
}
tp_result tp_dict_open_unchecked(tp_dict **out, const uint8_t *buf, size_t len);

Opens without CRC-32 verification. Use for trusted data (e.g., ROM) or when you need maximum open speed.

tp_result tp_dict_close(tp_dict **dict);

Closes the dictionary and sets *dict = NULL.

Look Up Keys

tp_result tp_dict_lookup(const tp_dict *dict, const char *key, tp_value *val);

Looks up a NUL-terminated key. On success, fills val with the decoded value. Returns TP_ERR_NOT_FOUND if the key doesn’t exist.

String values are zero-copy: val.data.string_val.str points directly into the .trp buffer. Do not free it.

tp_value val;
if (tp_dict_lookup(dict, "name", &val) == TP_OK) {
    if (val.type == TP_STRING)
        printf("name = %.*s\n", (int)val.data.string_val.str_len,
               val.data.string_val.str);
}
tp_result tp_dict_lookup_n(const tp_dict *dict, const char *key, size_t key_len,
                            tp_value *val);

Look up a key with explicit length. Use when keys may contain NUL bytes.

tp_result tp_dict_contains(const tp_dict *dict, const char *key, bool *out);

Checks whether a key exists without decoding its value. Faster than tp_dict_lookup() when you only need membership testing.

bool found = false;
tp_dict_contains(dict, "flag", &found);
if (found) { /* key exists */ }

Dictionary Info

uint32_t tp_dict_count(const tp_dict *dict);

Returns the number of keys stored in the dictionary.

tp_result tp_dict_get_info(const tp_dict *dict, tp_dict_info *info);

Retrieves metadata about the dictionary: format version, key count, addressing modes, checksum type, bits-per-symbol, and total size.

tp_dict_info info;
tp_dict_get_info(dict, &info);
printf("Format: v%u.%u\n", info.format_version_major, info.format_version_minor);
printf("Keys:   %u\n", info.num_keys);
printf("BPS:    %u\n", info.bits_per_symbol);
printf("Size:   %zu bytes\n", info.total_bytes);

The tp_dict_info struct:

Field Type Description
format_version_major uint8_t Major version (must be 1)
format_version_minor uint8_t Minor version
num_keys uint32_t Number of keys
trie_mode tp_addr_mode Trie addressing mode
value_mode tp_addr_mode Value store addressing mode
checksum_type tp_checksum_type Checksum algorithm
bits_per_symbol uint8_t Bits per trie symbol
has_values bool Whether values are stored
has_suffix_table bool Whether suffix table is present
compact_mode bool Whether compact mode is used
total_bytes size_t Total encoded size in bytes

triepack::Dict

RAII wrapper around tp_dict. Include <triepack/triepack.hpp>.

#include <triepack/triepack.hpp>

triepack::Dict dict(data, size);

int32_t val;
if (dict.lookup("apple", &val)) {
    // val == 42
}

size_t n = dict.size();  // number of keys
Method Description
Dict(const uint8_t *data, size_t size) Open a .trp blob
bool lookup(const char *key, int32_t *val) Look up key, return true if found
size_t size() Number of entries
tp_dict *handle() Access the underlying C handle

Move semantics are supported.

triepack.decode(buf)

Decodes a .trp buffer and returns a Python dict:

import triepack

buf = open("data.trp", "rb").read()
data = triepack.decode(buf)

# data is a plain dict
print(data["name"])     # "Alice"
print(data["count"])    # 42
print(len(data))        # number of keys

Parameters:

  • buf (bytes): A .trp encoded blob.

Returns: dict – keys are strings, values are native Python types.

Raises: ValueError on corrupt data, bad magic, or truncated input.

There is no separate “open” step or manual memory management.

triepack.decode(buf)

Decodes a .trp buffer and returns a plain object:

const triepack = require('triepack');
const fs = require('fs');

const buf = fs.readFileSync('data.trp');
const data = triepack.decode(buf);

console.log(data.name);   // "Alice"
console.log(data.count);  // 42
console.log(Object.keys(data).length);  // number of keys

Parameters:

  • buf (Uint8Array or Buffer): A .trp encoded blob.

Returns: Object – keys are strings, values are native JS types.

Throws: Error on corrupt data, bad magic, or truncated input.

There is no separate “open” step or manual memory management.


Iterator

Iterators traverse dictionary entries without allocating result arrays.

tp_result tp_dict_iterate(const tp_dict *dict, tp_iterator **out);

Creates an iterator over all keys in the dictionary. Entries are returned in lexicographic order.

tp_result tp_iter_next(tp_iterator *it, const char **key, size_t *key_len,
                        tp_value *val);

Advances to the next entry. Returns TP_ERR_EOF when done. The key pointer is valid until the next call to tp_iter_next() or tp_iter_destroy().

tp_result tp_iter_reset(tp_iterator *it);

Resets the iterator to the beginning.

tp_result tp_iter_destroy(tp_iterator **it);

Destroys the iterator and sets *it = NULL.

Example – iterating all entries:

tp_iterator *it = NULL;
tp_dict_iterate(dict, &it);

const char *key;
size_t key_len;
tp_value val;

while (tp_iter_next(it, &key, &key_len, &val) == TP_OK) {
    printf("%.*s\n", (int)key_len, key);
}

tp_iter_destroy(&it);

triepack::Iterator

RAII wrapper around tp_iterator.

triepack::Iterator it(dict);
while (it.next()) {
    printf("%s -> %d\n", it.key(), it.value());
}
Method Description
Iterator(const Dict &dict) Create iterator over dict
bool next() Advance; returns false when done
const char *key() Current key
int32_t value() Current value

Not applicable. The Python decode() function returns a plain dict, so you iterate with standard Python:

data = triepack.decode(buf)

for key, value in sorted(data.items()):
    print(f"{key} = {value}")

Not applicable. The JavaScript decode() function returns a plain object, so you iterate with standard JS:

const data = triepack.decode(buf);

for (const [key, value] of Object.entries(data).sort()) {
    console.log(`${key} = ${value}`);
}

tp_result tp_dict_find_prefix(const tp_dict *dict, const char *prefix,
                               tp_iterator **out);

Returns an iterator over all keys that start with prefix. Results are in lexicographic order. Use tp_iter_next() to consume.

tp_result tp_dict_find_fuzzy(const tp_dict *dict, const char *query,
                              uint8_t max_dist, tp_iterator **out);

Returns an iterator over all keys within Levenshtein edit distance max_dist of query. Recommended: max_dist <= 2 for performance.

tp_result tp_iter_get_distance(const tp_iterator *it, uint8_t *dist);

Gets the edit distance for the current iterator position. Only valid for iterators created by tp_dict_find_fuzzy().

The C++ wrappers use the same C search functions via the underlying handle:

tp_iterator *it = NULL;
tp_dict_find_prefix(dict.handle(), "app", &it);
// ... use tp_iter_next(it, ...) ...
tp_iter_destroy(&it);

C/C++ only. Search is not exposed in the Python binding. Use standard Python filtering on the decoded dict:

data = triepack.decode(buf)

# Prefix search
matches = {k: v for k, v in data.items() if k.startswith("app")}

C/C++ only. Search is not exposed in the JavaScript binding. Use standard JS filtering on the decoded object:

const data = triepack.decode(buf);

// Prefix search
const matches = Object.fromEntries(
    Object.entries(data).filter(([k]) => k.startsWith("app"))
);

Bitstream

Low-level arbitrary-width bit field I/O. You rarely need this directly – the encoder and dictionary use it internally – but it’s available for custom binary formats or direct trie manipulation.

32-Bit Platform Portability

The bitstream API uses uint64_t and int64_t for values, positions, and stream lengths. C99 makes these exact-width types optional – they are only required when the platform has a native 64-bit integer type. TriePack provides a fallback typedef from unsigned long long / long long (guaranteed by C99 to be at least 64 bits) so the library compiles on 32-bit targets (i386, 68000, MIPS32, ESP32/Xtensa, ARM32) even if their <stdint.h> does not define uint64_t directly.

For narrow reads (up to 32 bits), use tp_bs_read_bits32() to avoid the uint64_t intermediate entirely.

Reader Lifecycle

tp_result tp_bs_reader_create(tp_bitstream_reader **out,
                               const uint8_t *buf, uint64_t bit_len);

Creates a reader over an existing buffer. Zero-copy – the reader reads directly from buf, which must remain valid. bit_len is the number of valid bits (not bytes).

uint8_t data[] = {0xAB, 0xCD};
tp_bitstream_reader *r = NULL;
tp_bs_reader_create(&r, data, 16);  /* 16 bits = 2 bytes */
tp_result tp_bs_reader_create_copy(tp_bitstream_reader **out,
                                    const uint8_t *buf, uint64_t bit_len);

Creates a reader that copies the buffer into owned memory. Use when buf may be freed before you’re done reading.

tp_result tp_bs_reader_destroy(tp_bitstream_reader **reader);

Destroys the reader and sets *reader = NULL.

tp_result tp_bs_reader_set_bit_order(tp_bitstream_reader *r, tp_bit_order order);

Sets MSB-first (default) or LSB-first bit ordering for subsequent reads.

Writer Lifecycle

tp_result tp_bs_writer_create(tp_bitstream_writer **out,
                               size_t initial_cap, size_t growth);

Creates a growable writer. initial_cap is the initial buffer size in bytes (0 = use default 256). growth is the growth increment in bytes (0 = double on each realloc).

tp_bitstream_writer *w = NULL;
tp_bs_writer_create(&w, 1024, 0);  /* 1KB initial, doubling growth */
tp_result tp_bs_writer_destroy(tp_bitstream_writer **writer);

Destroys the writer and frees its buffer.

Bit-Level I/O

tp_result tp_bs_write_bits(tp_bitstream_writer *w, uint64_t value, uint8_t n);

Writes the low n bits (1-64) of value to the stream, MSB first.

tp_bs_write_bits(w, 0x15, 5);  /* writes 10101 (5 bits) */
tp_bs_write_bits(w, 0x03, 3);  /* writes 011 (3 bits) */
/* stream now contains: 10101011 = 0xAB */
tp_result tp_bs_read_bits(tp_bitstream_reader *r, uint8_t n, uint64_t *out);

Reads n bits (1-64) as an unsigned value.

uint64_t val;
tp_bs_read_bits(r, 5, &val);  /* reads 5 bits -> val */
tp_result tp_bs_write_bits_signed(tp_bitstream_writer *w, int64_t value, uint8_t n);

Writes the low n bits of a signed value. The value is truncated to n bits in two’s complement form.

tp_result tp_bs_read_bits_signed(tp_bitstream_reader *r, uint8_t n, int64_t *out);

Reads n bits as a sign-extended value. The MSB of the n-bit field is treated as the sign bit and extended to fill the int64_t.

Example – signed bit field round-trip (like a 5-bit signed integer):

tp_bitstream_writer *w = NULL;
tp_bs_writer_create(&w, 256, 0);

/* Write three signed values as 5-bit fields (range: -16 to +15) */
tp_bs_write_bits_signed(w, -3, 5);   /* stores 11101 */
tp_bs_write_bits_signed(w,  7, 5);   /* stores 00111 */
tp_bs_write_bits_signed(w, -1, 5);   /* stores 11111 */

/* Read them back -- sign extension restores the original values */
tp_bitstream_reader *r = NULL;
tp_bs_writer_to_reader(w, &r);

int64_t val;
tp_bs_read_bits_signed(r, 5, &val);  /* val == -3  */
tp_bs_read_bits_signed(r, 5, &val);  /* val ==  7  */
tp_bs_read_bits_signed(r, 5, &val);  /* val == -1  */

tp_bs_reader_destroy(&r);
tp_bs_writer_destroy(&w);

This works exactly like reading a narrow signed field from a hardware register or binary protocol – the MSB is the sign bit, and the library extends it to the full int64_t width on read.

tp_result tp_bs_read_bit(tp_bitstream_reader *r, uint8_t *out);
tp_result tp_bs_write_bit(tp_bitstream_writer *w, uint8_t value);

Read/write a single bit.

tp_result tp_bs_peek_bits(tp_bitstream_reader *r, uint8_t n, uint64_t *out);

Reads n bits without advancing the cursor. Useful for lookahead.

tp_result tp_bs_read_bits32(tp_bitstream_reader *r, uint8_t n, uint32_t *out);

Reads up to 32 bits into a uint32_t. On 32-bit processors (i386, 68k, MIPS32, ESP32) this avoids 64-bit register-pair emulation. Ideal for reading trie symbols, control codes, and other fields that fit in 32 bits.

Byte-Level I/O

These require the cursor to be byte-aligned. They read/write in big-endian (network) byte order.

tp_result tp_bs_read_u8(tp_bitstream_reader *r, uint8_t *out);
tp_result tp_bs_read_u16(tp_bitstream_reader *r, uint16_t *out);
tp_result tp_bs_read_u32(tp_bitstream_reader *r, uint32_t *out);
tp_result tp_bs_read_u64(tp_bitstream_reader *r, uint64_t *out);
tp_result tp_bs_read_bytes(tp_bitstream_reader *r, uint8_t *buf, size_t n);
tp_result tp_bs_write_u8(tp_bitstream_writer *w, uint8_t val);
tp_result tp_bs_write_u16(tp_bitstream_writer *w, uint16_t val);
tp_result tp_bs_write_u32(tp_bitstream_writer *w, uint32_t val);
tp_result tp_bs_write_u64(tp_bitstream_writer *w, uint64_t val);
tp_result tp_bs_write_bytes(tp_bitstream_writer *w, const uint8_t *buf, size_t n);

Example – writing a binary header:

tp_bs_write_u32(w, 0x54525000);  /* magic "TRP\0" */
tp_bs_write_u8(w, 1);            /* version major */
tp_bs_write_u8(w, 0);            /* version minor */
tp_bs_write_u16(w, flags);       /* flags */

VarInt

Variable-length integer encoding. Small values use fewer bytes.

tp_result tp_bs_write_varint_u(tp_bitstream_writer *w, uint64_t value);
tp_result tp_bs_read_varint_u(tp_bitstream_reader *r, uint64_t *out);

Unsigned VarInt (LEB128): 7 data bits + 1 continuation bit per byte. Values 0-127 take 1 byte, 128-16383 take 2 bytes, etc.

tp_result tp_bs_write_varint_s(tp_bitstream_writer *w, int64_t value);
tp_result tp_bs_read_varint_s(tp_bitstream_reader *r, int64_t *out);

Signed VarInt: zigzag-encodes the value first, then LEB128. This makes small negative numbers cheap (e.g., -1 takes 1 byte, not 10).

Example – VarInt round-trip:

tp_bs_write_varint_u(w, 300);
tp_bs_write_varint_s(w, -42);

/* ... create reader from writer ... */

uint64_t u;
tp_bs_read_varint_u(r, &u);   /* u == 300 */

int64_t s;
tp_bs_read_varint_s(r, &s);   /* s == -42 */

Symbols and UTF-8

tp_result tp_bs_write_symbol(tp_bitstream_writer *w, uint32_t val, uint8_t bps);
tp_result tp_bs_read_symbol(tp_bitstream_reader *r, uint8_t bps, uint32_t *out);

Write/read a fixed-width symbol of bps bits (1-32). Used internally for trie symbols where bps is determined by the alphabet size.

tp_result tp_bs_write_utf8(tp_bitstream_writer *w, uint32_t codepoint);
tp_result tp_bs_read_utf8(tp_bitstream_reader *r, uint32_t *out);

Write/read a Unicode codepoint in standard UTF-8 encoding (1-4 bytes). The stream must be byte-aligned. Rejects overlong encodings and surrogates.

Cursor Operations

uint64_t tp_bs_reader_position(const tp_bitstream_reader *r);

Returns the current read position in bits.

uint64_t tp_bs_reader_remaining(const tp_bitstream_reader *r);

Returns the number of bits remaining from the cursor to the end.

uint64_t tp_bs_reader_length(const tp_bitstream_reader *r);

Returns the total stream length in bits.

tp_result tp_bs_reader_seek(tp_bitstream_reader *r, uint64_t bit_pos);

Seeks to an absolute bit position. Returns TP_ERR_INVALID_POSITION if out of range.

tp_result tp_bs_reader_advance(tp_bitstream_reader *r, uint64_t n);

Advances the cursor by n bits.

tp_result tp_bs_reader_align_to_byte(tp_bitstream_reader *r);

Advances to the next byte boundary. No-op if already aligned.

bool tp_bs_reader_is_byte_aligned(const tp_bitstream_reader *r);

Returns true if the cursor is on a byte boundary.

uint64_t tp_bs_writer_position(const tp_bitstream_writer *w);
tp_result tp_bs_writer_align_to_byte(tp_bitstream_writer *w);

Writer equivalents for position and byte alignment.

Stateless ROM Functions

These operate on raw byte pointers with no reader object. No allocation, no state. Safe for interrupt handlers and bare-metal.

tp_result tp_bs_read_bits_at(const uint8_t *buf, uint64_t bit_pos,
                              uint8_t n, uint64_t *out);

Reads n bits at the given bit offset from buf.

tp_result tp_bs_read_bits_signed_at(const uint8_t *buf, uint64_t bit_pos,
                                     uint8_t n, int64_t *out);

Reads n bits as a sign-extended value at the given bit offset.

tp_result tp_bs_read_varint_u_at(const uint8_t *buf, uint64_t bit_pos,
                                  uint64_t *out, uint8_t *bits_read);

Reads a VarInt at the given bit offset. bits_read receives the number of bits consumed, so you can advance your own cursor.

Example – reading from a ROM buffer:

static const uint8_t rom_data[] = { /* ... */ };
uint64_t val;
tp_bs_read_bits_at(rom_data, 256, 5, &val);  /* read 5 bits at bit 256 */

Buffer Management

tp_result tp_bs_writer_get_buffer(const tp_bitstream_writer *w,
                                   const uint8_t **buf, uint64_t *bit_len);

Gets a read-only view of the writer’s buffer. The pointer is valid until the next write or destroy.

tp_result tp_bs_writer_detach_buffer(tp_bitstream_writer *w,
                                      uint8_t **buf, size_t *byte_len,
                                      uint64_t *bit_len);

Transfers buffer ownership to the caller. The writer becomes empty. The caller must free() the buffer.

tp_result tp_bs_writer_to_reader(tp_bitstream_writer *w,
                                  tp_bitstream_reader **reader);

Creates a reader over the writer’s contents (copies the data). Useful for round-trip testing.

tp_result tp_bs_reader_get_buffer(const tp_bitstream_reader *r,
                                   const uint8_t **buf, uint64_t *bit_len);

Gets a read-only view of the reader’s underlying buffer.

tp_result tp_bs_reader_direct_ptr(tp_bitstream_reader *r,
                                   const uint8_t **ptr, size_t n);

Returns a pointer directly into the reader’s buffer at the current position. Requires byte alignment. Advances the cursor by n * 8 bits. Used for zero-copy string/blob access.

tp_result tp_bs_writer_append_buffer(tp_bitstream_writer *w,
                                      const uint8_t *buf, uint64_t bit_len);

Appends raw bits from an external buffer.

tp_result tp_bs_copy_bits(tp_bitstream_reader *r, tp_bitstream_writer *w,
                           uint64_t n_bits);

Copies n_bits from reader to writer.

triepack::BitstreamReader / triepack::BitstreamWriter

RAII wrappers around the C bitstream API. Include <triepack/bitstream.hpp>.

#include <triepack/bitstream.hpp>

// Writer
triepack::BitstreamWriter w(1024);
w.write_bits(0x15, 5);
w.write_bits(0x03, 3);

// Reader from writer
triepack::BitstreamReader r = w.to_reader();
uint64_t val;
r.read_bits(5, &val);  // val == 0x15

See the C tab for the full list of operations – the C++ wrappers provide matching method names.

Internal. The bitstream layer is not part of the Python public API. Use triepack.encode() and triepack.decode() instead.

Internal. The bitstream layer is not part of the JavaScript public API. Use triepack.encode() and triepack.decode() instead.


JSON

The JSON library encodes JSON documents into .trp format using flattened dot-path keys. JSON objects become dictionaries with keys like "address.city" and "items[0].name".

One-Shot Encode

tp_result tp_json_encode(const char *json_str, size_t json_len,
                          uint8_t **buf, size_t *buf_len);

Parses a JSON string and encodes it as a .trp blob. The output buffer is heap-allocated – caller must free() it.

const char *json = "{\"name\":\"Alice\",\"age\":30}";
uint8_t *buf = NULL;
size_t buf_len = 0;
tp_result rc = tp_json_encode(json, strlen(json), &buf, &buf_len);
if (rc == TP_OK) {
    /* buf contains buf_len bytes of .trp data */
    free(buf);
}

One-Shot Decode

tp_result tp_json_decode(const uint8_t *buf, size_t buf_len,
                          char **json_str, size_t *json_len);

Decodes a .trp blob back to a compact JSON string. Caller must free() the returned string.

tp_result tp_json_decode_pretty(const uint8_t *buf, size_t buf_len,
                                 const char *indent,
                                 char **json_str, size_t *json_len);

Decodes to a pretty-printed JSON string. indent sets the per-level indentation (e.g., " " for 2 spaces, "\t" for tabs).

char *pretty = NULL;
size_t plen = 0;
tp_json_decode_pretty(buf, buf_len, "  ", &pretty, &plen);
printf("%.*s\n", (int)plen, pretty);
free(pretty);

DOM-Style Access

tp_result tp_json_open(tp_json **out, const uint8_t *buf, size_t buf_len);

Opens a .trp buffer for DOM-style path lookups. Copies the buffer internally. Call tp_json_close() when done.

tp_result tp_json_close(tp_json **json);

Closes the JSON handle and sets *json = NULL.

tp_result tp_json_lookup_path(const tp_json *j, const char *path, tp_value *val);

Looks up a value by dot/bracket path. Paths match the flattened key format used internally.

tp_json *j = NULL;
tp_json_open(&j, buf, buf_len);

tp_value val;
tp_json_lookup_path(j, "name", &val);
/* val.type == TP_STRING, val.data.string_val.str == "Alice" */

tp_json_lookup_path(j, "items[0].price", &val);
/* val.type == TP_INT or TP_FLOAT64, depending on the JSON number */

tp_json_close(&j);
tp_result tp_json_root_type(const tp_json *j, tp_value_type *type);

Returns TP_DICT if the root is a JSON object, TP_ARRAY if it’s an array.

uint32_t tp_json_count(const tp_json *j);

Returns the number of top-level keys (object) or elements (array).

tp_result tp_json_iterate(const tp_json *j, tp_iterator **out);

Returns an iterator over the top-level entries.

triepack::Json

The C++ JSON wrapper (in development) will provide RAII access to the JSON encode/decode functions. For now, use the C API directly:

#include <triepack/triepack_json.h>

uint8_t *buf = nullptr;
size_t buf_len = 0;
tp_result rc = tp_json_encode(json_str, json_len, &buf, &buf_len);
if (rc == TP_OK) {
    // ... use buf ...
    free(buf);
}

Not applicable. The Python binding uses triepack.encode() / triepack.decode() for all data, including JSON-like structures. Pass a nested dict to encode structured data:

import triepack

data = {
    "name": "Alice",
    "address.city": "Portland",
    "scores[0]": 95,
    "scores[1]": 87,
}

buf = triepack.encode(data)
result = triepack.decode(buf)

For full JSON support (automatic flattening/unflattening of nested structures), use the C or C++ JSON API.

Not applicable. The JavaScript binding uses triepack.encode() / triepack.decode() for all data. Pass a plain object:

const triepack = require('triepack');

const data = {
    name: "Alice",
    "address.city": "Portland",
    "scores[0]": 95,
    "scores[1]": 87,
};

const buf = triepack.encode(data);
const result = triepack.decode(buf);

For full JSON support (automatic flattening/unflattening of nested structures), use the C or C++ JSON API.


C++ Wrappers (triepack:: namespace)

RAII wrappers around the C API. Include <triepack/triepack.hpp> for core wrappers and <triepack/bitstream.hpp> for bitstream wrappers. All wrappers are non-copyable and movable.

Move Semantics

All wrappers support move construction and move assignment:

triepack::Encoder enc1;
enc1.insert("key", 1);

triepack::Encoder enc2 = std::move(enc1);
// enc1 is now empty (handle() == nullptr)
// enc2 owns the encoder

See the C++ tabs in each section above for per-class details.