|
| 1 | +defmodule PrintableBinary do |
| 2 | + @moduledoc """ |
| 3 | + Compile-time `~PB` sigil that decodes printable-binary glyphs into a raw binary |
| 4 | + at COMPILE time — so binary data can be embedded legibly inline in source (no |
| 5 | + separate fixture files), with zero runtime cost (the bytes are baked into the |
| 6 | + BEAM). |
| 7 | +
|
| 8 | + import PrintableBinary |
| 9 | + @magic ~PB"..." # decoded to raw bytes at compile time |
| 10 | + """ |
| 11 | + |
| 12 | + @map_path Path.expand("../../character_map.txt", __DIR__) |
| 13 | + @external_resource @map_path |
| 14 | + |
| 15 | + # byte<->glyph map parsed from the single-source character_map.txt at compile time |
| 16 | + # (same parse rules as every other implementation: `##` = comment, first |
| 17 | + # whitespace-delimited token per line is the glyph, in byte order). |
| 18 | + @glyph_to_byte @map_path |
| 19 | + |> File.read!() |
| 20 | + |> String.split("\n") |
| 21 | + |> Enum.map(&String.trim_trailing(&1, "\r")) |
| 22 | + |> Enum.reject(&(&1 == "" or String.starts_with?(&1, "##"))) |
| 23 | + |> Enum.map(&(&1 |> String.split() |> hd())) |
| 24 | + |> Enum.with_index() |
| 25 | + |> Map.new(fn {glyph, byte} -> {glyph, byte} end) |
| 26 | + |
| 27 | + @doc """ |
| 28 | + Decode a printable-binary string to a raw binary. Whitespace is ignored (the |
| 29 | + encoding never emits literal whitespace), so wrapped / heredoc input works. |
| 30 | + Raises `ArgumentError` on an unrecognized glyph. |
| 31 | + """ |
| 32 | + @spec decode(binary()) :: binary() |
| 33 | + def decode(string) when is_binary(string) do |
| 34 | + string |
| 35 | + |> String.replace(~r/[ \t\r\n]/, "") |
| 36 | + |> String.codepoints() |
| 37 | + |> Enum.map(fn cp -> |
| 38 | + case Map.fetch(@glyph_to_byte, cp) do |
| 39 | + {:ok, byte} -> byte |
| 40 | + :error -> raise ArgumentError, "invalid printable-binary glyph: #{inspect(cp)}" |
| 41 | + end |
| 42 | + end) |
| 43 | + |> :erlang.list_to_binary() |
| 44 | + end |
| 45 | + |
| 46 | + @doc ~S''' |
| 47 | + The `~PB` sigil: decodes printable-binary content to a raw binary at compile |
| 48 | + time. Use `"..."` or a `"""` heredoc (a literal `"` never appears in the |
| 49 | + payload, so it can't close the sigil early). |
| 50 | +
|
| 51 | + iex> import PrintableBinary |
| 52 | + iex> ~PB"AB" |
| 53 | + "AB" |
| 54 | + ''' |
| 55 | + defmacro sigil_PB({:"<<>>", _meta, [string]}, _modifiers) when is_binary(string) do |
| 56 | + decode(string) |
| 57 | + end |
| 58 | +end |
0 commit comments