This is a binary parser. It uses C-bitfield like syntax to guide parsing. Moreover, it carries a data structure Bitvector that represents bits and allows conversion between different types.
A simple example:
let spec =
#|a : 3;
#|b : 4;
// 7'b1011_100
let viewer = Bitviewer::new(b"\x5c");
let map = viewer.extract(spec); // a: 4, b: 11
// This is because `a` is 3'b100 = 4, and `b` is 4'b1011, which is 11.
It also allows specifying big and little endian. The default is little endian. In future versions, it might be configurable.
The following example turns a little endian int32_t to big endian.
let spec = "rev: >32";
let viewer = Bitviewer::new(Bitvector::from(0x12345678).raw())
let map = viewer.extract(spec);
inspect(map.get("rev").unwrap().to(0), content="\{0x78563412}");
Moreover, if you want to specify the exact location, you can do it:
// RISC-V instruction format, R-type
let spec =
#|opcode: 0..6;
#|rd    : 7..11;
#|funct3: 12..14;
#|rs    : 15..19;
#|rs2   : 20..24;
#|funct7: 25..31;
// RISC-V: add x5, x10, x22
// opcode = 0b0110011 (0x33), funct3 = funct7 = 0
let viewer = Bitviewer::new(Bitvector::from(0x016502b3).raw());
let map = viewer.extract(spec);
inspect(map, content={
  "opcode": b"\x33",
  "rd": b"\x05",
  "funct3": b"\x00",
  "rs": b"\x0a", // 10
  "rs2": b"\x16", // 22
  "funct7": b"\x00",
}.to_string())
Note the .. is inclusive on both ends.
Besides extracting bits, you can also build up bits from a specification:
let spec =
#|h1 : 3*5-4+1;
#|h2 : 4;
let viewer = Bitviewer::build(spec, {
  "h1": 0x234,
  "h2": 1
});
// This will return a bitvector.
viewer.raw();
// The content is 0x3412, because it's little endian by default. As an int, it's 0x1234.
For a detailed usage introduction, see the bitvector and bitviewer sections below.
You can view it as an easy conversion layer between different integer types and byte arrays. It doesn't support bit operations on its own. If you want that, please use kesmeey's immut_bitvector, though it might have gone outdated.
You can use Bitvector::from(x) for various types of x, including integer types, boolean, Bytes, byte array and the corresponding array views. The method raw() copies the content into Bytes, and view() returns its internal representation Array[Byte].
To convert it into other types, you can use the to method, and supply a value of type T as its argument to make it return type T. For example, bv.to(0) returns an Int, and bv.to('_') returns a Char.
The method set(from, to, bv) will copy the content of the argument bv to the bits [from:to]. Moreover, you can use the view syntax bv[from:to] to extract bits from the bitvector.
The specification language goes as follows:
Statement ::= Ident `:` Spec `;`;
Spec ::= (`<` | `>`)? Expr;
Expr ::= Add `..` Add | Add;
Add ::= Mul ((`+` | `-`) Mul)*;
Mul ::= Primary ((`*` | `/`) Primary)*;
Primary ::= `(` Expr `)` | Number | Ident;
Ident ::= [A-Za-z0-9][A-Za-z0-9_]*;
Number ::= [0-9]+;
In human language, this means each statement is of the form something : 1+2*(3-a);. You can refer to values that are previously read (for example a length field).
Note that the ; at the end is mandatory. Without it, the parser will IGNORE everything after the first statement that is not semicolon-terminated.
You can use < and > to specify little/big endianness, but it is only applicable on bit-length that is a multiple of 8.
For building only, you can specify literals with names starting with =, and followed by a binary string. For extraction, this will be recognized as a normal name (and cannot be referred later, because it is not alphanumeric). As an example,
let spec =
#|a   : 5;
#|=011: 3;
#|b   : 6;
#|=01 : 2;
This describes a 2-byte UTF-8 encoding scheme. Note the order of bits goes from LSB to MSB, which means it might be the opposite from what you're familiar with: 110xxxxx 10xxxxxx (because this is written from MSB to LSB). The byte order is the same as you might expect, with 110 at the front and 10 at the back.