BLOB's
======
BLOB's (**B**inary **L**arge **OB**jects), used to hold packed arrays of bytes, have built-in
support in Rhai.
A BLOB has no literal representation, but is created via the `blob` function, or simply returned as
the result of a function call (e.g. `generate_thumbnail_image` that generates a thumbnail version of
a large image as a BLOB).
All items stored in a BLOB are bytes (i.e. `u8`) and the BLOB can freely grow or shrink with bytes
added or removed.
[`type_of()`](type-of.md) a BLOB returns `"blob"`.
Element Access Syntax
---------------------
### From beginning
Like [arrays](arrays.md), BLOB's are accessed with zero-based, non-negative integer indices:
> _blob_ `[` _index position from 0 to length−1_ `]`
### From end
A _negative_ position accesses an element in the BLOB counting from the _end_, with −1 being the
_last_ element.
> _blob_ `[` _index position from −1 to −length_ `]`
```admonish info.small "Byte values"
The value of a particular byte in a BLOB is mapped to an integer.
Only the lowest 8 bits are significant, all other bits are ignored.
```
Create a BLOB
-------------
The function `blob` allows creating an empty BLOB, optionally filling it to a required size with a
particular value (default zero).
```rust
let x = blob(); // empty BLOB
let x = blob(10); // BLOB with ten zeros
let x = blob(50, 42); // BLOB with 50x 42's
```
```admonish tip "Tip: Initialize with byte stream"
To quickly initialize a BLOB with a particular byte stream, the `write_be` method can be used to
write eight bytes at a time (four under 32-bit) in big-endian byte order.
If fewer than eight bytes are needed, remember to right-pad the number as big-endian byte order is used.
~~~rust
let buf = blob(12, 0); // BLOB with 12x zeros
// Write eight bytes at a time, in big-endian order
buf.write_be(0, 8, 0xab_cd_ef_12_34_56_78_90);
buf.write_be(8, 8, 0x0a_0b_0c_0d_00_00_00_00);
// ^^^^^^^^^^^ remember to pad unused bytes
print(buf); // prints "[abcdef1234567890 0a0b0c0d]"
buf[3] == 0x12;
buf[10] == 0x0c;
// Under 'only_i32', write four bytes at a time:
buf.write_be(0, 4, 0xab_cd_ef_12);
buf.write_be(4, 4, 0x34_56_78_90);
buf.write_be(8, 4, 0x0a_0b_0c_0d);
~~~
```
Writing ASCII Bytes
-------------------
```admonish warning.side "Non-ASCII"
Non-ASCII characters (i.e. characters not within 1-127) are ignored.
```
For many embedded applications, it is necessary to encode an ASCII [string](strings-chars.md) as a
byte stream.
Use the `write_ascii` method to write ASCII [strings](strings-chars.md) into any specific
[range](ranges.md) within a BLOB.
The following is an example of a building a 16-byte command to send to an embedded device.
```rust
// Assume the following 16-byte command for an embedded device:
// ┌─────────┬───────────────┬──────────────────────────────────┬───────┐
// │ 0 │ 1 │ 2-13 │ 14-15 │
// ├─────────┼───────────────┼──────────────────────────────────┼───────┤
// │ command │ string length │ ASCII string, max. 12 characters │ CRC │
// └─────────┴───────────────┴──────────────────────────────────┴───────┘
let buf = blob(16, 0); // initialize command buffer
let text = "foo & bar"; // text string to send to device
buf[0] = 0x42; // command code
buf[1] = s.len(); // length of string
buf.write_ascii(2..14, text); // write the string
let crc = buf.calc_crc(); // calculate CRC
buf.write_le(14, 2, crc); // write CRC
print(buf); // prints "[4209666f6f202620 626172000000abcd]"
// ^^ command code ^^^^ CRC
// ^^ string length
// ^^^^^^^^^^^^^^^^^^^ foo & bar
device.send(buf); // send command to device
```
```admonish question.small "What if I need UTF-8?"
The `write_utf8` function writes a string in UTF-8 encoding.
UTF-8, however, is not very common for embedded applications.
```
Built-in Functions
------------------
The following functions operate on BLOB's.
| Functions | Parameter(s) | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `blob` constructor function |
- _(optional)_ initial length of the BLOB
- _(optional)_ initial byte value
| creates a new BLOB, optionally of a particular length filled with an initial byte value (default = 0) |
| `to_array` | _none_ | converts the BLOB into an [array](arrays.md) of integers |
| `as_string` | _none_ | converts the BLOB into a [string](strings-chars.md) (the byte stream is interpreted as UTF-8) |
| `get` | position, counting from end if < 0 | gets a copy of the byte at a certain position (0 if the position is not valid) |
| `set` | - position, counting from end if < 0
- new byte value
| sets a certain position to a new value (no effect if the position is not valid) |
| `push`, `append`, `+=` operator | - BLOB
- byte to append
| appends a byte to the end |
| `append`, `+=` operator | - BLOB
- BLOB to append
| concatenates the second BLOB to the end of the first |
| `append`, `+=` operator | - BLOB
- [string/character](strings-chars.md) to append
| concatenates a [string/character](strings-chars.md) (as UTF-8 encoded byte-stream) to the end of the BLOB |
| `+` operator | - first BLOB
- [string](strings-chars.md) to append
| creates a new [string](strings-chars.md) by concatenating the BLOB (as UTF-8 encoded byte-stream) with the the [string](strings-chars.md) |
| `+` operator | - [string](strings-chars.md)
- BLOB to append
| creates a new [string](strings-chars.md) by concatenating the BLOB (as UTF-8 encoded byte-stream) to the end of the [string](strings-chars.md) |
| `+` operator | - first BLOB
- second BLOB
| concatenates the first BLOB with the second |
| `==` operator | - first BLOB
- second BLOB
| are two BLOB's the same? |
| `!=` operator | - first BLOB
- second BLOB
| are two BLOB's different? |
| `insert` | - position, counting from end if < 0, end if ≥ length
- byte to insert
| inserts a byte at a certain position |
| `pop` | _none_ | removes the last byte and returns it (0 if empty) |
| `shift` | _none_ | removes the first byte and returns it (0 if empty) |
| `extract` | - start position, counting from end if < 0, end if ≥ length
- _(optional)_ number of bytes to extract, none if ≤ 0
| extracts a portion of the BLOB into a new BLOB |
| `extract` | [range](ranges.md) of bytes to extract, from beginning if ≤ 0, to end if ≥ length | extracts a portion of the BLOB into a new BLOB |
| `remove` | position, counting from end if < 0 | removes a byte at a particular position and returns it (0 if the position is not valid) |
| `reverse` | _none_ | reverses the BLOB byte by byte |
| `len` method and property | _none_ | returns the number of bytes in the BLOB |
| `is_empty` method and property | _none_ | returns `true` if the BLOB is empty |
| `pad` | - target length
- byte value to pad
| pads the BLOB with a byte value to at least a specified length |
| `clear` | _none_ | empties the BLOB |
| `truncate` | target length | cuts off the BLOB at exactly a specified length (discarding all subsequent bytes) |
| `chop` | target length | cuts off the head of the BLOB, leaving the tail at exactly a specified length |
| `contains`, `in` operator | byte value to find | does the BLOB contain a particular byte value? |
| `split` | - BLOB
- position to split at, counting from end if < 0, end if ≥ length
| splits the BLOB into two BLOB's, starting from a specified position |
| `drain` | - start position, counting from end if < 0, end if ≥ length
- number of bytes to remove, none if ≤ 0
| removes a portion of the BLOB, returning the removed bytes as a new BLOB |
| `drain` | [range](ranges.md) of bytes to remove, from beginning if ≤ 0, to end if ≥ length | removes a portion of the BLOB, returning the removed bytes as a new BLOB |
| `retain` | - start position, counting from end if < 0, end if ≥ length
- number of bytes to retain, none if ≤ 0
| retains a portion of the BLOB, removes all other bytes and returning them as a new BLOB |
| `retain` | [range](ranges.md) of bytes to retain, from beginning if ≤ 0, to end if ≥ length | retains a portion of the BLOB, removes all other bytes and returning them as a new BLOB |
| `splice` | - start position, counting from end if < 0, end if ≥ length
- number of bytes to remove, none if ≤ 0
- BLOB to insert
| replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
| `splice` | - [range](ranges.md) of bytes to remove, from beginning if ≤ 0, to end if ≥ length
- BLOB to insert | replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
| `parse_le_int` |
- start position, counting from end if < 0, end if ≥ length
- number of bytes to parse, 8 if > 8 (4 under 32-bit), none if ≤ 0
| parses an integer at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_le_int` | [range](ranges.md) of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit) | parses an integer at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_be_int` | - start position, counting from end if < 0, end if ≥ length
- number of bytes to parse, 8 if > 8 (4 under 32-bit), none if ≤ 0
| parses an integer at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_be_int` | [range](ranges.md) of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit) | parses an integer at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_le_float` | - start position, counting from end if < 0, end if ≥ length
- number of bytes to parse, 8 if > 8 (4 under 32-bit), none if ≤ 0
| parses a floating-point number at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_le_float` | [range](ranges.md) of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit) | parses a floating-point number at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_be_float` | - start position, counting from end if < 0, end if ≥ length
- number of bytes to parse, 8 if > 8 (4 under 32-bit), none if ≤ 0
| parses a floating-point number at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_be_float` | [range](ranges.md) of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit) | parses a floating-point number at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_le` | - start position, counting from end if < 0, end if ≥ length
- number of bytes to write, 8 if > 8 (4 under 32-bit), none if ≤ 0
- integer or floating-point value
| writes a value at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_le` | - [range](ranges.md) of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit)
- integer or floating-point value
| writes a value at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_be` | - start position, counting from end if < 0, end if ≥ length
- number of bytes to write, 8 if > 8 (4 under 32-bit), none if ≤ 0
- integer or floating-point value
| writes a value at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_be` | - [range](ranges.md) of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit)
- integer or floating-point value
| writes a value at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_utf8` | - start position, counting from end if < 0, end if ≥ length
- number of bytes to write, none if ≤ 0, to end if ≥ length
- [string](strings-chars.md) to write
| writes a [string](strings-chars.md) to the particular offset in UTF-8 encoding |
| `write_utf8` | - [range](ranges.md) of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length
- [string](strings-chars.md) to write
| writes a [string](strings-chars.md) to the particular offset in UTF-8 encoding |
| `write_ascii` | - start position, counting from end if < 0, end if ≥ length
- number of [characters](strings-chars.md) to write, none if ≤ 0, to end if ≥ length
- [string](strings-chars.md) to write
| writes a [string](strings-chars.md) to the particular offset in 7-bit ASCII encoding (non-ASCII [characters](strings-chars.md) are skipped) |
| `write_ascii` | - [range](ranges.md) of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length
- [string](strings-chars.md) to write
| writes a [string](strings-chars.md) to the particular offset in 7-bit ASCII encoding (non-ASCII [characters](strings-chars.md) are skipped) |