RESP parser with binary pattern matching
Every other language in this series wrote a cursor-tracking, manual byte-extracting parser. Elixir compresses all of that into pattern match clauses. The compiler does the bounds checking for free.
Quick anatomy of the wire language before you read the parser, because every match clause below keys off it. RESP tags each value with a single leading byte and ends every part with \r\n. A + starts a simple string like +OK\r\n. A - starts an error. A : starts an integer like :42\r\n. A $ starts a bulk string, which is a length then the bytes, like $4\r\nPING\r\n. A * starts an array with an element count, like *1\r\n followed by its elements. A client command is just an array of bulk strings, so PING on the wire is *1\r\n$4\r\nPING\r\n. Each clause you are about to read handles one of those leading bytes.
def parse(<<>>), do: {:incomplete, <<>>}
def parse(<<"+", rest::binary>>), do: parse_line(rest, :simple)
def parse(<<":", rest::binary>>) do
case parse_line(rest, :raw) do
{{:raw, s}, rem} -> {{:int, String.to_integer(s)}, rem}
other -> other
end
end
def parse(<<"$", rest::binary>>) do
case parse_line(rest, :raw) do
{{:raw, hdr}, rem} ->
n = String.to_integer(hdr)
case rem do
<<payload::binary-size(n), "\r\n", tail::binary>> -> {{:bulk, payload}, tail}
_ -> {:incomplete, <<"$", rest::binary>>}
end
other -> other
end
end
def parse(<<"*", rest::binary>>), do: # recursive array parseThe RESP parser. One match clause per RESP type byte.
<<payload::binary-size(n), \u0022\\r\\n\u0022, tail::binary>> is a length-validated, CRLF-terminated extraction in one match. If the buffer is shorter than n+2 bytes, the match fails and we fall through to :incomplete. The compiler verifies the bit alignment.
Quiz: Quiz
Loading practice…
AI prompt: Try it: hand-write a frame
Loading practice…
Checkpoint: Network checkpoint
Loading practice…