bitty

Package Version Hex Docs

A binary and string parser combinator library for Gleam that tries to be zero-copy when it can be.

You probably don’t want this library. Gleam already has excellent parsing capabilities with features like bit arrays and pattern matching. For strings the excellent splitter library can be very useful.

Installation

gleam add bitty

Quick Example

Parsing a simple TLV (tag-length-value) structure

Using Gleam’s use syntax for monadic bind:

import bitty as p
import bitty/bytes as b
import bitty/num

pub type Tlv {
  Tlv(tag: Int, length: Int, value: BitArray)
}

pub fn tlv() -> p.Parser(Tlv) {
  use tag <- p.then(num.u8())
  use len <- p.then(num.u8())
  use value <- p.then(b.take(len))

  p.success(Tlv(tag, len, value))
}

Core Concepts

Parser

pub opaque type Parser(a)

Parsers operate over BitArray and track:

Running a parser

pub fn run(parser: Parser(a), on input: BitArray)
  -> Result(a, BittyError)

pub fn run_partial(parser: Parser(a), on input: BitArray)
  -> Result(#(a, BitArray), BittyError)

Length-Bounded Parsing

Critical for TLV-style formats:

pub fn within_bytes(byte_len: Int, run inner: Parser(a))
  -> Parser(a)

Parse exactly byte_len bytes as a sub-stream.

Used for:

Numeric Decoding

import bitty/num

num.u8()
num.u16(num.BigEndian)
num.u32(num.LittleEndian)
num.u64(num.BigEndian)

All integer parsers return Int. On the JavaScript target, 64-bit values above 2^53 - 1 (9,007,199,254,740,991) may lose precision due to IEEE 754 double-precision floating-point limitations.

Bit-Level Parsing

import bitty/bits

use flag <- p.then(bits.bit())
use value <- p.then(bits.uint(5))
use _ <- p.then(p.align())

p.success(#(flag, value))

Byte-aligned parsing is the fast path. Bit parsing is explicit and opt-in.

String Parsing

import bitty as p
import bitty/string as s

pub fn request_line() -> p.Parser(#(String, String, String)) {
  use method <- p.then(s.alpha1())
  use _ <- p.then(s.literal(" "))
  use path <- p.then(s.take_while1(fn(c) { c != " " }))
  use _ <- p.then(s.literal(" "))
  use version <- p.then(s.not_line_ending())

  p.success(#(method, path, version))
}

Error Reporting

Structured error type:

pub type BittyError {
  BittyError(
    at: Location,
    expected: List(String),
    context: List(String),
    message: Option(String),
  )
}

Add readable labels:

p.label(parser, named: "ASN.1 length")
p.context(parser, in: "TLS handshake")

You get precise error locations and meaningful context stacks.

✨ Search Document