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.

02-resp-parser/server.exs
elixir
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 parse

The 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…