# dtob-doc
C implementation of the DTOB binary format (Delineated Trinary Over Binary).
## Architecture
```
lib/ public headers (dtob.h, json.h, dtob_types.h, html.h)
src/ library source → builds into libdtob.a
trit.c trit encoding/decoding (byte ↔ 00/01/10 pairs, 11 = padding)
lexer.c byte-aligned tokenizer (ctrl words, data payloads)
parser.c tokens → DtobValue AST (handles types header, custom types)
ast.c AST construction, accessors, memory, DtobWriter, type registry
encoder.c DtobValue AST → encoded bytes
encode_json.c JSON text → DtobValue AST
encode_xml.c XML/HTML text → DtobValue AST
html_rules.c HTML void elements, raw text tags, implicit close rules
cli/ command-line tools (not part of libdtob.a)
main.c dtob dispatch (encode, represent, extract, info)
main_encode.c dtob-encode (JSON/XML/HTML → .dtob)
main_represent.c dtob-represent (.dtob → human-readable)
represent.c colored representation output
typed_encode.c schema inference for typed encoding
greedy.c HTML asset inlining (images, CSS, scripts)
http.c URL fetching via libcurl
io.c file I/O helpers
test/ test files
```
## Wire format
- every document starts with magic bytes `13032026` (ASCII, not trit-encoded)
- control words: 2 bytes, top 3 bits = `110`, 13-bit code
- data: trit-encoded (00/01/10 pairs), padded to byte boundary with `11` pairs
- codes 0-15: built-in (open, close_arr, close_kv, close_types, raw, float, double, int types)
- codes 16-8190: custom (must be declared in types header)
- code 8191: blast (ignored, for analog resilience)
- see spec.bnf for the full grammar
## Custom types
Applications define custom types via a types header at the start of the document. Three kinds:
- **nullable** (0 opcodes): just the code, no payload — used as flags/sentinels
- **enum** (2+ opcodes): code + inner_code selects which variant
- **struct** (ordered fields): code + open + field values + close_arr
Use `dtob_types.h` macros (`DTOB_DEFINE_CUSTOM_TYPES`, `DTOB_CUSTOM_TYPE_RAW`, etc.) to define types without boilerplate.
## JSON convention layer
`json.h` provides suggested code assignments for JSON primitives (codes 16-19). These are NOT reserved — applications can freely reassign codes 16+ for their own types. If you use json.h codes, the types header must declare them.
Use `dtob_raw()` instead of `json_string()` when you don't need the JSON convention layer — raw values don't require a types header.
## Key API patterns
```c
// decode
DtobValue *root = dtob_decode(buf, len); // auto-parses types header
DtobValue *root = dtob_decode_with_types(buf, len, &types); // also returns types
// encode
uint8_t *out = dtob_encode(root, &out_len); // no types header
uint8_t *out = dtob_encode_with_types(root, &types, &out_len); // with types header
// AST construction
DtobValue *v = dtob_raw(data, len);
DtobValue *v = dtob_int(42);
DtobValue *v = dtob_uint(100);
DtobValue *v = dtob_array();
DtobValue *kvs = dtob_kvset();
dtob_array_push(arr, val);
dtob_kvset_put(kvs, "key", val);
// KV-set accessors
DtobValue *v = dtob_kvset_get(kvs, "key");
uint64_t n = dtob_kvset_uint(kvs, "key");
size_t len = dtob_kvset_str(kvs, "key", buf, sizeof(buf));
// DtobWriter — for manual stream construction (used by prlents for types header)
DtobWriter w;
dtob_writer_init(&w);
dtob_writer_ctrl(&w, DTOB_CODE_OPEN);
dtob_writer_data(&w, bytes, len);
dtob_writer_value(&w, val); // without type resolution
dtob_writer_value_typed(&w, val, &types); // with type resolution
// memory
dtob_free(root);
```
## Build
```
make # builds libdtob.a + CLI tools
make test # runs test suite
```
No external dependencies for `libdtob.a` (just libc + libm). The `dtob-encode` CLI needs libcurl for URL fetching.
## Downstream users
- **prlents** — links libdtob.a, uses custom types (ENTS_NAME, ENTS_FILE, etc.) with DtobWriter for manual stream construction including types header
- **glass** — links libdtob.a transitively through libprlents.a
- **alock** — links libdtob.a for lock state serialization
## Common pitfalls
- `dtob_encode()` does NOT write a types header. If your AST contains custom-typed values, the output will fail to decode. Use `dtob_encode_with_types()` or `DtobWriter` to manually write the types header.
- `json_string()` creates values with custom code 16. If you use `dtob_encode()` without a types header, decoders will reject code 16. Use `dtob_raw()` for plain strings that don't need the JSON convention.
- `dtob_kvset_put()` replaces existing keys — safe to call multiple times with the same key.
- all `dtob_*` constructors allocate — caller must `dtob_free()` the root when done.