bitty/string
String-level parsers for reading, matching, and scanning UTF-8 text.
These parsers decode graphemes on the fly.
Use bitty/bytes for raw byte operations and bitty/bits for sub-byte
access before returning to string parsing.
Values
pub fn alpha() -> bitty.Parser(String)
Parse zero or more ASCII alphabetic characters (a-zA-Z).
Returns "" when no characters match.
let assert Ok(#(s, rest)) = bitty.run_partial(string.alpha(), on: <<"abcXYZ123">>)
assert s == "abcXYZ"
assert rest == <<"123">>
pub fn alpha1() -> bitty.Parser(String)
Parse one or more ASCII alphabetic characters (a-zA-Z).
Fails if the first character is not alphabetic.
let assert Ok(#(s, rest)) = bitty.run_partial(string.alpha1(), on: <<"abcXYZ123">>)
assert s == "abcXYZ"
assert rest == <<"123">>
pub fn alphanumeric() -> bitty.Parser(String)
Parse zero or more ASCII alphanumeric characters (a-zA-Z0-9).
Returns "" when no characters match.
let assert Ok(#(s, rest)) = bitty.run_partial(string.alphanumeric(), on: <<"abc123!!">>)
assert s == "abc123"
assert rest == <<"!!">>
pub fn alphanumeric1() -> bitty.Parser(String)
Parse one or more ASCII alphanumeric characters (a-zA-Z0-9).
Fails if the first character is not alphanumeric.
let assert Ok(#(s, rest)) = bitty.run_partial(string.alphanumeric1(), on: <<"abc123!!">>)
assert s == "abc123"
assert rest == <<"!!">>
pub fn crlf() -> bitty.Parser(Nil)
Match a carriage return + line feed sequence (\r\n).
let assert Ok(#(_, rest)) = bitty.run_partial(string.crlf(), on: <<"\r\nhello">>)
assert rest == <<"hello">>
pub fn digit() -> bitty.Parser(String)
Parse zero or more ASCII decimal digits (0-9).
Returns "" when no characters match.
let assert Ok(#(s, rest)) = bitty.run_partial(string.digit(), on: <<"42abc">>)
assert s == "42"
assert rest == <<"abc">>
pub fn digit1() -> bitty.Parser(String)
Parse one or more ASCII decimal digits (0-9).
Fails if the first character is not a digit.
let assert Ok(#(s, rest)) = bitty.run_partial(string.digit1(), on: <<"42abc">>)
assert s == "42"
assert rest == <<"abc">>
pub fn fixed(byte_len: Int) -> bitty.Parser(String)
Parse a fixed-width string field, trimming trailing NUL bytes and spaces. Useful for binary formats that pad string fields to a fixed size.
let assert Ok(s) = bitty.run(string.fixed(5), on: <<"hi", 0x00, 0x00, 0x00>>)
assert s == "hi"
pub fn grapheme() -> bitty.Parser(String)
Parse a single UTF-8 grapheme and return it as a one-character string. Handles multi-byte and multi-codepoint grapheme clusters.
let assert Ok(c) = bitty.run(string.grapheme(), on: <<"A">>)
assert c == "A"
pub fn grapheme_if(
predicate: fn(String) -> Bool,
) -> bitty.Parser(String)
Parse a single UTF-8 grapheme that satisfies the given predicate. Fails if the grapheme doesn’t match or if the input is not valid UTF-8.
let parser = string.grapheme_if(fn(c) { c == "A" })
let assert Ok(c) = bitty.run(parser, on: <<"A">>)
assert c == "A"
pub fn hex_digit() -> bitty.Parser(String)
Parse zero or more ASCII hexadecimal digits (0-9a-fA-F).
Returns "" when no characters match.
let assert Ok(#(s, rest)) = bitty.run_partial(string.hex_digit(), on: <<"deadBEEF!">>)
assert s == "deadBEEF"
assert rest == <<"!">>
pub fn hex_digit1() -> bitty.Parser(String)
Parse one or more ASCII hexadecimal digits (0-9a-fA-F).
Fails if the first character is not a hex digit.
let assert Ok(#(s, rest)) = bitty.run_partial(string.hex_digit1(), on: <<"deadBEEF!">>)
assert s == "deadBEEF"
assert rest == <<"!">>
pub fn integer() -> bitty.Parser(Int)
Parse one or more ASCII digits and convert them to an Int.
Fails if no digits are found or if parsing fails.
let assert Ok(#(n, rest)) = bitty.run_partial(string.integer(), on: <<"42abc">>)
assert n == 42
assert rest == <<"abc">>
pub fn line_ending() -> bitty.Parser(Nil)
Match a line ending: either \r\n or \n.
Tries \r\n first to avoid partial matches.
let assert Ok(#(_, rest)) = bitty.run_partial(string.line_ending(), on: <<"\r\nhello">>)
assert rest == <<"hello">>
pub fn literal(expected: String) -> bitty.Parser(Nil)
Match an exact string literal at the current position and consume it.
Returns Nil on success; fails if the bytes don’t match.
let assert Ok(Nil) = bitty.run(string.literal("GET"), on: <<"GET">>)
pub fn multispace() -> bitty.Parser(String)
Parse zero or more whitespace characters (space, tab, \r, \n).
Returns "" when no characters match.
let assert Ok(s) = bitty.run(string.multispace(), on: <<" \t\n">>)
assert s == " \t\n"
pub fn multispace1() -> bitty.Parser(String)
Parse one or more whitespace characters (space, tab, \r, \n).
Fails if the first character is not whitespace.
let assert Ok(#(s, rest)) = bitty.run_partial(string.multispace1(), on: <<" \t\nhello">>)
assert s == " \t\n"
assert rest == <<"hello">>
pub fn newline() -> bitty.Parser(Nil)
Match a newline character (\n).
let assert Ok(#(_, rest)) = bitty.run_partial(string.newline(), on: <<"\nhello">>)
assert rest == <<"hello">>
pub fn not_line_ending() -> bitty.Parser(String)
Consume characters until a \r or \n is found.
The line ending itself is not consumed.
let assert Ok(#(s, rest)) = bitty.run_partial(string.not_line_ending(), on: <<"hello\nworld">>)
assert s == "hello"
assert rest == <<"\nworld">>
pub fn null_terminated() -> bitty.Parser(String)
Parse a null-terminated (C-style) string. Consumes graphemes until a
NUL byte (0x00) is found, then skips the NUL. Fails if no NUL is present.
let assert Ok(s) = bitty.run(string.null_terminated(), on: <<"hi", 0x00>>)
assert s == "hi"
pub fn space() -> bitty.Parser(String)
Parse zero or more spaces and tabs.
Returns "" when no characters match.
let assert Ok(#(s, rest)) = bitty.run_partial(string.space(), on: <<" \thello">>)
assert s == " \t"
assert rest == <<"hello">>
pub fn space1() -> bitty.Parser(String)
Parse one or more spaces and tabs. Fails if the first character is not a space or tab.
let assert Ok(#(s, rest)) = bitty.run_partial(string.space1(), on: <<" \thello">>)
assert s == " \t"
assert rest == <<"hello">>
pub fn tab() -> bitty.Parser(Nil)
Match a tab character (\t).
let assert Ok(#(_, rest)) = bitty.run_partial(string.tab(), on: <<"\thello">>)
assert rest == <<"hello">>
pub fn take_graphemes(count: Int) -> bitty.Parser(String)
Parse exactly count UTF-8 graphemes and return them as a string.
Unlike utf8 which counts bytes, this counts logical characters.
let assert Ok(s) = bitty.run(string.take_graphemes(2), on: <<"café">>)
assert s == "ca"
pub fn take_until(
predicate: fn(String) -> Bool,
) -> bitty.Parser(String)
Consume graphemes until the predicate matches, returning everything
before the match. The matching grapheme is not consumed.
Returns "" when the first grapheme matches.
let parser = string.take_until(fn(c) { c == " " })
let assert Ok(#(s, rest)) = bitty.run_partial(parser, on: <<"hello world">>)
assert s == "hello"
assert rest == <<" world">>
pub fn take_while(
predicate: fn(String) -> Bool,
) -> bitty.Parser(String)
Consume graphemes while the predicate holds, returning them as a string.
Returns "" when zero graphemes match.
let parser = string.take_while(fn(c) { c != " " })
let assert Ok(#(s, rest)) = bitty.run_partial(parser, on: <<"hello world">>)
assert s == "hello"
assert rest == <<" world">>
pub fn take_while1(
predicate: fn(String) -> Bool,
) -> bitty.Parser(String)
Like take_while but requires at least one matching grapheme.
Fails if the first grapheme doesn’t satisfy the predicate.
let parser = string.take_while1(fn(c) { c == "a" })
let assert Ok(#(s, _rest)) = bitty.run_partial(parser, on: <<"aab">>)
assert s == "aa"
pub fn utf8(byte_len: Int) -> bitty.Parser(String)
Parse exactly byte_len bytes and decode them as a UTF-8 string.
Fails if the bytes are not valid UTF-8 or if there is insufficient input.
let assert Ok(s) = bitty.run(string.utf8(5), on: <<"hello">>)
assert s == "hello"