synta 0.3.2

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# Documentation Snippet Validation

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Tools]#tools
  - [C / C++ validator]#c-c-validator
  - [C / C++ analyser]#c-c-analyser
  - [Rust validator]#rust-validator
- [Prerequisites]#prerequisites
- [Quick Start]#quick-start
  - [C / C++ samples]#c-c-samples
    - [Options]#options
  - [C / C++ block analyser]#c-c-block-analyser
    - [Options]#options-1
  - [Rust samples]#rust-samples
    - [Options]#options-2
- [How It Works — C / C++]#how-it-works-c-c
  - [Step 1 — Extraction and classification (`doc-c-samples.py`)]#step-1-extraction-and-classification-doc-c-samplespy
  - [Step 2 — Compilation (`doc-c-samples.sh`)]#step-2-compilation-doc-c-samplessh
  - [Step 3 — Combined retry]#step-3-combined-retry
  - [Step 4 — Reporting]#step-4-reporting
- [Fragment Wrapper]#fragment-wrapper
- [How It Works — Rust]#how-it-works-rust
  - [Step 1 — Extraction and classification (`doc-rust-samples.py`)]#step-1-extraction-and-classification-doc-rust-samplespy
  - [Step 2 — Type-checking (`doc-rust-samples.sh`)]#step-2-type-checking-doc-rust-samplessh
  - [Step 3 — Combined retry]#step-3-combined-retry-1
  - [Step 4 — Reporting]#step-4-reporting-1
- [Rust Fragment Wrapper]#rust-fragment-wrapper
- [Adding New Documentation Snippets]#adding-new-documentation-snippets
  - [C / C++ snippets]#c-c-snippets
  - [Rust snippets]#rust-snippets

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

This directory contains tooling that extracts every C, C++, and Rust code block
from the Synta documentation, compiles each one with a real compiler, and
reports failures with a precise source location.

## Tools

### C / C++ validator

| File | Role |
|------|------|
| `doc-c-samples.sh` | Driver: collects markdown files, invokes the extractor, compiles each block, prints results |
| `doc-c-samples.py` | Extractor: parses markdown, classifies and wraps each block, writes compilable source files, a compile manifest, and a skipped manifest |

### C / C++ analyser

| File | Role |
|------|------|
| `doc-c-analyze.sh` | Driver: collects markdown files, invokes the extractor, then shows a classification analysis of every block |
| `doc-c-analyze.py` | Reporter: reads the manifests produced by the extractor and formats the analysis output |

### Rust validator

| File | Role |
|------|------|
| `doc-rust-samples.sh` | Driver: collects markdown files, invokes the extractor, type-checks each block via a temporary Cargo project, prints results |
| `doc-rust-samples.py` | Extractor: parses markdown, classifies and wraps each block, writes compilable source files and a manifest |

All scripts must reside in the same directory.  Each shell script locates its
companion Python script relative to itself, so they can be invoked from any
working directory.

## Prerequisites

- **Bash 4+** — required by all shell scripts. macOS users may need to install a newer bash via Homebrew (`brew install bash`), as macOS ships with bash 3.2. Each script checks the version on startup and exits with an error if bash 3 or earlier is detected.
- **Rust toolchain** — needed to build `synta-ffi` (C validator) and for `cargo check` (Rust validator)
- **gcc** (or another C compiler) and **g++** (or another C++ compiler) — for the C validator
- **python3** ≥ 3.9

Build the FFI crate once before the first run of the C validator:

```sh
cargo build -p synta-ffi
```

The Rust validator builds synta automatically on its first run.

## Quick Start

### C / C++ samples

Validate every workspace markdown file (default):

```sh
./contrib/validation/doc-c-samples.sh
```

Validate a single file and see every block (not just failures):

```sh
./contrib/validation/doc-c-samples.sh -v docs/C_API.md
```

Restrict the search to a single directory:

```sh
./contrib/validation/doc-c-samples.sh --docs-dir docs/
```

Use a different compiler:

```sh
./contrib/validation/doc-c-samples.sh --cc clang --cxx clang++
```

#### Options

| Option | Default | Description |
|--------|---------|-------------|
| `--docs-dir DIR` | workspace-wide | Directory searched for `*.md` files instead of scanning the whole workspace |
| `--header-dir DIR` | `include/` | Directory containing `synta.h` |
| `--cc COMPILER` | `$CC` or `gcc` | C compiler |
| `--cxx COMPILER` | `$CXX` or `g++` | C++ compiler |
| `--verbose`, `-v` | off | Print a result line for every block, not just failures |
| `FILE.md ...` || Process only these files instead of scanning the workspace |

### C / C++ block analyser

Show why each block in `docs/*.md` was classified, with a preview of every
skipped block:

```sh
./contrib/validation/doc-c-analyze.sh
```

Analyse a single file, also listing compilable blocks:

```sh
./contrib/validation/doc-c-analyze.sh --show-compilable docs/C_API.md
```

The analyser never invokes a compiler — it is a pure classification report,
useful when adding new documentation snippets or investigating skipped blocks.

#### Options

| Option | Default | Description |
|--------|---------|-------------|
| `--docs-dir DIR` | `docs/` | Directory searched for `*.md` files |
| `--show-compilable` | off | Also list every compilable block, not just skipped ones |
| `FILE.md ...` || Process only these files instead of the whole docs directory |

### Rust samples

Validate every workspace markdown file (default):

```sh
./contrib/validation/doc-rust-samples.sh
```

Validate a single file and see every block (not just failures):

```sh
./contrib/validation/doc-rust-samples.sh -v docs/usage.md
```

Restrict the search to a single directory:

```sh
./contrib/validation/doc-rust-samples.sh --docs-dir docs/
```

Also compile serde integration blocks (requires the `serde` feature):

```sh
./contrib/validation/doc-rust-samples.sh --features-serde
```

#### Options

| Option | Default | Description |
|--------|---------|-------------|
| `--docs-dir DIR` | workspace-wide | Directory searched for `*.md` files instead of scanning the whole workspace |
| `--features-serde` | off | Also compile `serde_json` blocks (enables `synta/serde`) |
| `--verbose`, `-v` | off | Print a result line for every block, not just failures |
| `FILE.md ...` || Process only these files instead of scanning the workspace |

Set `NO_COLOR=1` to suppress ANSI colour codes in the output.

Exit status is **0** if all blocks compiled successfully, **1** if any failed.

## How It Works — C / C++

### Step 1 — Extraction and classification (`doc-c-samples.py`)

The Python script reads each markdown file and collects every fenced code block
whose language tag is `c`, `cpp`, or `c++`.  Each block is then classified:

| Kind | Criterion | Action |
|------|-----------|--------|
| `skip_nonsynta` | No `synta_` / `Synta*` identifier | Skip — not a Synta API example |
| `skip_foreign` | OpenSSL or libtasn1 API calls detected | Skip — migration comparison code |
| `skip_pitfall` | Block contains both `// WRONG` and `// RIGHT` markers | Skip — intentional anti-pattern, compiler would flag the wrong half |
| `skip_pseudocode` | Block uses `...` as a function-call argument | Skip — illustrative placeholder, not real C |
| `skip_header_doc` | Block reproduces `synta.h` type or function declarations | Skip — would cause redeclaration errors alongside `#include <synta.h>` |
| `program` | Block contains `int main(` | Compile as a self-contained translation unit |
| `toplevel` | Block starts with a function/struct/typedef definition | Compile at file scope |
| `fragment` | Anything else | Wrap in a harness function with pre-declared variables |

For each compilable block the extractor writes two files into a temporary
directory:

- **`src/NNNNN.{c,cpp}`** — the wrapped, ready-to-compile translation unit
- **`src/NNNNN.combined.{c,cpp}`** — all preceding `toplevel`/`program` blocks
  from the same doc file prepended before the current block (generated only
  when there are preceding blocks); used for the retry step below

A manifest (`manifest.tsv`) lists one compilable block per line with seven
tab-separated fields:

```
doc_file  start_line  lang  src_file  kind  raw_file  combined_file
```

Only compilable blocks appear in the manifest; skipped blocks are excluded
entirely to avoid tab-field collapsing when bash reads the TSV.

### Step 2 — Compilation (`doc-c-samples.sh`)

The shell script reads the manifest and runs the compiler with
`-fsyntax-only` (full type-check, no object file) on each source file.

**Compiler flags applied to every block:**

```
-std=c11 / -std=c++17
-Wall -Wextra -Wformat=2
-Werror=implicit-function-declaration   (catches misspelled function names)
-Werror=incompatible-pointer-types      (catches wrong argument types)
-Werror=int-conversion                  (catches integer/pointer confusion)
-Werror=format                          (catches wrong printf specifiers)
-Wno-unused-variable -Wno-unused-but-set-variable
-Wno-unused-function -Wno-unused-parameter -Wno-shadow
```

Include paths: `include/` (for `synta.h`) and `synta-ffi/examples/c/` (for
generated headers such as `certificate.h`).

### Step 3 — Combined retry

If a block fails to compile standalone, the script retries with the combined
file (all preceding `toplevel`/`program` blocks of the same doc file
prepended).  This handles the common case where an implementation block
references a struct type defined in a preceding header block of the same
document.  Local `#include "..."` lines are stripped from the current block
before combining, since the type definitions are already inlined from the
preceding blocks.

### Step 4 — Reporting

Each failure prints the originating file and line number, the block kind, and
the compiler error with the temporary file path replaced by the doc reference:

```
[FAIL] docs/C_API.md:142
       Language : c
       Kind     : fragment
       Errors:
         <docs/C_API.md:142>:5:5: error: ...
```

A summary line shows the total pass / fail / skip counts.

## Fragment Wrapper

Standalone statement blocks (`kind = fragment`) are wrapped in a harness
function so that declarations in the snippet can be type-checked without a
surrounding function body:

```c
#include <synta.h>
#include <stdio.h>  /* + other standard headers */

static int _synta_doc_sample(void) {
    /* ~50 pre-declared identifiers covering all variables used in doc snippets */
    SyntaDecoder *decoder = NULL;
    SyntaEncoder *encoder = NULL;
    SyntaByteArray output  = {NULL, 0, 0};
    /* ... */

    /* ── fragment (nested scope allows re-declaration) ── */
    {
        /* the raw code block goes here */
    }
    return 0;
}
```

The nested `{ }` scope allows the fragment to re-declare variables (e.g.
`SyntaDecoder *decoder = ...`) without clashing with the outer pre-declarations.

## How It Works — Rust

### Step 1 — Extraction and classification (`doc-rust-samples.py`)

The Python script reads each markdown file and collects every fenced code block
whose language tag is `rust` (with or without annotations).  Each block is
classified:

| Kind | Criterion | Action |
|------|-----------|--------|
| `skip_annotated` | Block has `ignore` or `compile_fail` annotation | Skip — explicitly not compilable |
| `skip_nonsynta` | No `synta` identifier found | Skip — not a Synta API example |
| `skip_nostd` | Block has `#![no_std]` | Skip — needs a cross-compilation target |
| `skip_foreign` | Uses a foreign ASN.1 crate (`der`, `asn1-rs`, `yasna`) | Skip — migration comparison code |
| `skip_serde` | Uses `serde_json` and `--features-serde` was not passed | Skip — needs serde feature |
| `program` | Block contains `fn main()` | Rename main, compile as a library crate |
| `toplevel` | Block starts with `fn`, `struct`, `impl`, `type`, etc. | Place at file scope |
| `fragment` | Anything else | Wrap in a harness function with pre-declared variables |

Blocks with annotation `no_run` (compile but do not execute) are still compiled.

For each compilable block the extractor writes two files:

- **`src/NNNNN.rs`** — the wrapped, ready-to-compile translation unit
- **`src/NNNNN.combined.rs`** — all preceding `toplevel`/`program` blocks from
  the same doc file prepended before the current block (generated only when
  there are preceding blocks); used for the retry step below

A manifest (`manifest.tsv`) lists one compilable block per line with seven
tab-separated fields:

```
doc_file  start_line  lang  src_file  kind  raw_file  combined_file
```

### Step 2 — Type-checking (`doc-rust-samples.sh`)

The shell script creates a temporary Cargo project whose `Cargo.toml` declares
synta as a path dependency.  Using the workspace's shared `target/` directory
means synta and its transitive dependencies are compiled at most once, keeping
per-snippet overhead to just re-analysing the tiny `src/lib.rs`.

For each snippet the script writes the wrapped source to
`$CRATE_DIR/src/lib.rs` and runs `cargo check --quiet`.

### Step 3 — Combined retry

If a snippet fails to check standalone, the script retries with the combined
file (all preceding `toplevel`/`program` blocks prepended at file scope).
This handles the case where a snippet references a type or helper function
defined in an earlier block of the same document.

### Step 4 — Reporting

Each failure prints the originating file and line number, the block kind, and
the compiler error with the generated file path replaced by the doc reference:

```
[FAIL] docs/usage.md:129
       Language : rust
       Kind     : fragment
       Errors:
         <docs/usage.md:129>:5:5: error: ...
```

A summary line shows the total pass / fail / skip counts.

## Rust Fragment Wrapper

Standalone statement blocks (`kind = fragment`) are wrapped in a harness
function that returns `std::result::Result` (fully-qualified to avoid shadowing
by `synta::Result<T>`) so that the `?` operator works without any annotation:

```rust,ignore
#![allow(unused_imports, unused_variables, /* … */)]
use synta::*;

fn _synta_doc_sample() -> std::result::Result<(), Box<dyn std::error::Error>> {
    // ── pre-declared identifiers for doc fragment compilation ──
    let data: Vec<u8> = vec![0x02, 0x01, 0x2a];
    let mut decoder = synta::Decoder::new(data.as_slice(), synta::Encoding::Der);
    let mut encoder = synta::Encoder::new(synta::Encoding::Der);
    let integer     = synta::Integer::from(0i64);

    // ── fragment (nested scope allows re-declaration) ──
    {
        /* the raw code block goes here */
    }
    Ok(())
}
```

The nested `{ }` scope allows the fragment to re-declare any of the
pre-declared identifiers (e.g. `let mut decoder = ...`) via shadowing rather
than causing a duplicate-binding error.

## Adding New Documentation Snippets

### C / C++ snippets

When you add a new C or C++ code block to the docs:

1. Run the validator to confirm it compiles:

   ```sh
   ./contrib/validation/doc-c-samples.sh -v docs/YOUR_FILE.md
   ```

2. If a variable used by your snippet is not among the pre-declared identifiers
   in `FRAGMENT_VARS` (in `doc-c-samples.py`), add it there.

3. If your snippet deliberately shows incorrect code alongside correct code,
   mark both sections with `// WRONG` and `// RIGHT` comments — the validator
   will skip the block automatically.

4. If your snippet is pseudocode (uses `...` as an argument placeholder),
   no action is needed; it is skipped automatically.

### Rust snippets

When you add a new Rust code block to the docs:

1. Run the validator to confirm it type-checks:

   ```sh
   ./contrib/validation/doc-rust-samples.sh -v docs/YOUR_FILE.md
   ```

2. If a variable used by your snippet is not among the pre-declared identifiers
   in `FRAGMENT_VARS` (in `doc-rust-samples.py`), add it there.

3. If your snippet uses `serde_json`, confirm it compiles with the serde flag:

   ```sh
   ./contrib/validation/doc-rust-samples.sh --features-serde -v docs/YOUR_FILE.md
   ```

4. If your snippet uses `#![no_std]`, it is skipped automatically (it would
   require a bare-metal cross-compilation target).

5. If your snippet is a migration comparison that also imports a foreign crate
   (`der`, `asn1-rs`, `yasna`), it is skipped automatically.

6. If your snippet deliberately shows code that should not compile (an
   anti-pattern or error case), add the `compile_fail` annotation to the
   fenced block opener:

   ````markdown
   ```rust,compile_fail
   // intentionally wrong code
   ```
   ````