# (struct pack)

This is an R6RS library for working with packed byte structures. It is
similar to `struct` in Python or `pack` and `unpack` in Perl.

All macros exported from this library may also be used as if they were
procedures.

## API

```Scheme
(import (struct pack))
```

This library uses format strings which specify binary fields. The
format strings are read left-to-right and have the following
interpretation:

 - `c` - s8, a signed byte;
 - `C` - u8, an unsigned byte;
 - `s` - s16, a signed 16-bit word;
 - `S` - u16, an unsigned 16-bit word;
 - `l` - s32, a signed 32-bit word;
 - `L` - u32, an unsigned 32-bit word;
 - `q` - s64, a signed 64-bit word;
 - `Q` - u64, an unsigned 64-bit word;
 - `f` - an IEEE-754 single-precision number;
 - `d` - an IEEE-754 double-precision number;
 - `x` - one byte of padding (zero);
 - `a` - enable automatic natural alignment (default);
 - `u` - disable automatic natural alignment;
 - `!` and `>` - the following fields will have big-endian
     (network) byte order;
 - `<` - the following fields will have little-endian byte order;
 - `=` - the following fields will have native endianness;
 - whitespace - ignored;
 - decimals - repeat the next format character N times.

By default fields are aligned. Padding bytes are inserted to align
fields to their natural alignment so that e.g. a 32-bit field is
aligned to a 4 byte offset.

### (unpack *fmt* *bytevector* [*offset*])

Returns as many values as there are fields in *fmt*. The values are
fetched from *bytevector* starting at *offset* (by default 0). For
example, if the format string is `"C"`, this translates into a
`bytevector-u8-ref` call.

A bug was fixed in version 1.1.1 that affected programs using an
unaligned offset. See the changelog for more information.

```Scheme
(import (struct pack))
(unpack "!xd" (pack "!xd" 3.14))
;; => 3.14
```

```Scheme
(number->string (unpack "!L" #vu8(#x00 #xFB #x42 #xE3)) 16)
;; => "FB42E3"
```

```Scheme
(unpack "!2CS" #vu8(1 2 0 3))
;; => 1
;; => 2
;; => 3
```

### (pack *fmt* *values* ...)

Returns a new bytevector containing the values encoded as per the
*fmt* string.

```Scheme
(pack "!CCS" 1 2 3)
;; => #vu8(1 2 0 3)
```

```Scheme
(pack "!CSC" 1 2 3)
;; => #vu8(1 0 0 2 3)
```

```scheme
(pack "!xd" 3.14)
;; => #vu8(0 0 0 0 0 0 0 0 64 9 30 184 81 235 133 31)
```

### (pack! *fmt* *bytevector* *offset* *values* ...)

The same as `pack`, except it modifies *bytevector* in place and
returns no values.

A bug was fixed in version 1.1.1 that affected programs using an
unaligned offset. See the changelog for more information.

### (get-unpack *binary-input-port* *fmt*)

Reads `(format-size fmt)` bytes from *binary-input-port* and unpacks
them according to the format string. Returns the same values as
`unpack` would.

### (put-pack *binary-output-port* *fmt* *values* ...)

Packs and writes the values to *binary-output-port*. Equivalent to
`(put-bytevector binary-output-port (pack fmt values ...)`, but may
be implemented more efficiently.

New in version 1.1.0.

### (format-size *fmt*)

The number of bytes the fields in the format string would use if
packed together, including any padding.

```Scheme
(format-size "!xQ")
;; => 16
```

```Scheme
(format-size "!uxQ")
;; => 9
```

## Expansion examples

This library is meant to be efficient and uses `syntax-case` to expand
the format string at expansion time. Here are some example uses of
this library that show the result from `expand/optimize` in Chez
Scheme (output from macro expansion followed by the cp0 optimizer).

The exact expansion is not part of the SemVer managed API.

### Simple packing

```scheme
(pack "!SS" (question-qtype x) (question-qclass x))
;; ==>
(let ([bv (make-bytevector 4)])
  (bytevector-u16-set! bv 2 (question-qclass x) 'big)
  (bytevector-u16-set! bv 0 (question-qtype x) 'big)
  bv)
```

### Port reading

```scheme
(get-unpack port "<u5S 3L SS")
;; ==>
(let ([bv (get-bytevector-n port 26)])
  (values (bytevector-u16-ref bv 0 'little)
    (bytevector-u16-ref bv 2 'little)
    (bytevector-u16-ref bv 4 'little)
    (bytevector-u16-ref bv 6 'little)
    (bytevector-u16-ref bv 8 'little)
    (bytevector-u32-ref bv 10 'little)
    (bytevector-u32-ref bv 14 'little)
    (bytevector-u32-ref bv 18 'little)
    (bytevector-u16-ref bv 22 'little)
    (bytevector-u16-ref bv 24 'little)))
```

```scheme
(get-unpack port "4xCCxCC7x")
;; ==>
(let ([bv (get-bytevector-n port 16)])
  (values
    (bytevector-u8-ref bv 4) (bytevector-u8-ref bv 5)
    (bytevector-u8-ref bv 7) (bytevector-u8-ref bv 8)))
```

Note that since the evaluation order is not specified, a sufficiently
smart compiler can get rid of all but one range check on the bytevector.

### Non-static offset

When the offset is not passed to the macro as an integer then the
expanded code dynamically computes the offset for each field. Note
that the format may also be non-static, in which case a run-time
procedure is called instead.

```scheme
(unpack "!uSS" bv end)
;; ==>
(let ([bv bv] [base-offset end])
  (values
    (bytevector-u16-ref bv (fx+ base-offset) 'big)
    (bytevector-u16-ref bv (fx+ 2 base-offset) 'big)))
```

```scheme
(pack! "cL" bv offset -1 42)
;; ==>
(let ([bv bv] [base-offset offset])
  (bytevector-u32-set! bv (fx+ 4 base-offset) 42 'little)
  (bytevector-u8-set! bv (fx+ 3 base-offset) 0)
  (bytevector-u8-set! bv (fx+ 2 base-offset) 0)
  (bytevector-u8-set! bv (fx+ 1 base-offset) 0)
  (bytevector-s8-set! bv (fx+ base-offset) -1)
  (values))
```
