|
17 | 17 | | **Zero-Copy Decoding** | Both eager and lazy decoding operate directly on the input buffer without copying | |
18 | 18 | | **Lazy Decoding** | `CBOR::Lazy` for on-demand nested access with key and result caching | |
19 | 19 | | **Streaming** | `CBOR.stream` for CBOR sequences from strings, files, and sockets | |
| 20 | +| **Diagnostic Notation** | `CBOR.diagnose` for RFC 8949 §8.1 human-readable output | |
20 | 21 | | **Class / Module Encoding** | Tag 49999 — classes and modules round-trip automatically | |
21 | 22 | | **Proc-based Tag Registration** | Register encode/decode procs for any type, including builtins with C state | |
22 | 23 | | **Performance** | ~30% faster than msgpack; 1.3–3× faster than simdjson for selective access | |
@@ -175,6 +176,55 @@ result.equal?(result[0]) # => true (self-referential) |
175 | 176 |
|
176 | 177 | --- |
177 | 178 |
|
| 179 | +### Diagnostic Notation |
| 180 | + |
| 181 | +`CBOR.diagnose` renders any CBOR buffer as RFC 8949 §8.1 diagnostic notation — a human-readable text format useful for debugging, logging, and test output. |
| 182 | + |
| 183 | +```ruby |
| 184 | +CBOR.diagnose(CBOR.encode(1)) # => "1" |
| 185 | +CBOR.diagnose(CBOR.encode("hello")) # => '"hello"' |
| 186 | +CBOR.diagnose(CBOR.encode([1, [2, 3]])) # => "[1,[2,3]]" |
| 187 | +CBOR.diagnose(CBOR.encode({ "a" => 1 })) # => '{"a":1}' |
| 188 | +CBOR.diagnose(CBOR.encode(true)) # => "true" |
| 189 | +CBOR.diagnose(CBOR.encode(nil)) # => "null" |
| 190 | + |
| 191 | +# Tags are rendered as tag(value) |
| 192 | +CBOR.diagnose("\xc1\x1a\x51\x4b\x67\xb0") # => "1(1363896240)" |
| 193 | + |
| 194 | +# Byte strings use hex notation |
| 195 | +CBOR.diagnose(CBOR.encode("\x01\x02\x03".b)) # => "h'010203'" |
| 196 | +``` |
| 197 | + |
| 198 | +**Float width suffixes** follow RFC 8610 §8.1: `_1` = f16, `_2` = f32, `_3` = f64. The suffix reflects the wire encoding, not the Ruby type. |
| 199 | + |
| 200 | +```ruby |
| 201 | +CBOR.diagnose(CBOR.encode(1.0)) # => "1.0_1" (f16) |
| 202 | +CBOR.diagnose(CBOR.encode(1.0e10)) # => "10000000000.0_2" (f32) |
| 203 | +CBOR.diagnose(CBOR.encode(3.14)) # => "3.14_3" (f64) |
| 204 | +CBOR.diagnose(CBOR.encode(Float::NAN)) # => "NaN_1" (canonical f16) |
| 205 | +CBOR.diagnose(CBOR.encode(Float::INFINITY)) # => "Infinity_1" |
| 206 | +CBOR.diagnose(CBOR.encode(-Float::INFINITY)) # => "-Infinity_1" |
| 207 | +``` |
| 208 | + |
| 209 | +**Indefinite-length items** (produced by other encoders) are rendered in diagnostic notation with the `_` prefix per spec: |
| 210 | + |
| 211 | +```ruby |
| 212 | +# Indefinite-length array from an external encoder |
| 213 | +CBOR.diagnose("\x9f\x01\x02\xff") # => "[_ 1,2]" |
| 214 | + |
| 215 | +# Indefinite-length map |
| 216 | +CBOR.diagnose("\xbf\x61a\x01\xff") # => "{_ \"a\":1}" |
| 217 | + |
| 218 | +# Indefinite-length byte string chunks |
| 219 | +CBOR.diagnose("\x5f\x41\x01\x41\x02\xff") # => "(_ h'01',h'02')" |
| 220 | +``` |
| 221 | + |
| 222 | +Note: `CBOR.encode` never produces indefinite-length items. `CBOR.diagnose` can read them from external sources for inspection purposes. |
| 223 | + |
| 224 | +**`MRB_NO_FLOAT` builds** render f16 and f32 values using exact rational arithmetic (no floating-point operations). f64 values use hex-float notation (e.g. `0x1.0p0_3`) as specified in RFC 8610 Appendix G. |
| 225 | + |
| 226 | +--- |
| 227 | + |
178 | 228 | ### Custom Tags & Type Registration |
179 | 229 |
|
180 | 230 | Register your own classes for CBOR encoding/decoding. The `native_ext_type` DSL declares which ivars to encode/decode and their expected Ruby type. Any Ruby class works as a type constraint — including other registered classes, enabling nested structures. |
|
0 commit comments