Skip to content

Commit d147967

Browse files
committed
feat(elixir): ~PB compile-time sigil demo + test-elixir CI guard
Embeds raw binary legibly inline in source: ~PB"..." decodes printable-binary glyphs to raw bytes at COMPILE time (zero runtime cost, bytes baked into BEAM). Map parsed from character_map.txt via @external_resource (single source of truth). CI test-elixir: mix test + MFIC cross-impl guard where the Zig CLI is the independent encoder oracle and Elixir decode/1 must reproduce originals over all-256 single bytes + 8 KiB random. elixir/_build gitignored.
1 parent 40735cd commit d147967

8 files changed

Lines changed: 174 additions & 0 deletions

File tree

.dirtree-state

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ ver=1.2
33
annotate=[
44
PLAN.md = Issue #1 plan: decode workflow + printable-binary-file.json container
55
docs/plans/2026-06-26-printable-binary-file-container-design.md = Design spec: printable-binary-file.json container + web decode workflow (issue #1)
6+
elixir/lib/printable_binary.ex = Elixir ~PB compile-time sigil: decodes printable-binary glyphs to raw bytes at compile time (reads character_map.txt via @external_resource); decode/1 runtime helper (whitespace-tolerant)
7+
elixir/mix.exs = Mix project for the ~PB sigil demo (no deps)
8+
elixir/test/printable_binary_test.exs = ExUnit tests + doctest for ~PB sigil / decode/1 (passthrough, control glyphs, whitespace-ignored heredoc, all-256 round-trip, unrecognized-glyph raise)
69
rust/build.rs = Codegens byte<->glyph map + O(1) decode tables from character_map.txt (single source)
710
rust/examples/bench.rs = In-process Rust codec throughput bench (isolates codec from CLI I/O)
811
rust/src/lib.rs = Rust printable-binary core: raw encode/decode + zero-alloc *_into (byte-identical to Zig, ~1.8x faster)

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,9 @@ inbox/
4848

4949
# Rust build artifacts
5050
/rust/target/
51+
52+
# Elixir build artifacts (~PB sigil demo)
53+
elixir/_build/
54+
elixir/deps/
55+
elixir/.elixir_ls/
56+
elixir/*.ez

PLAN.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,18 @@ Every printable-binary executable understands .pbf.json (-C/--container):
115115
container byte-identically, and vice versa)
116116
Shared: src/container_json.h (C), test/test_container (parameterized),
117117
test/test_container_cross (differential).
118+
119+
## Elixir ~PB compile-time sigil demo (2026-06-30) — DONE, green in CI
120+
Demonstrates embedding raw binary legibly inline in source (no fixture files):
121+
`import PrintableBinary; @magic ~PB"..."` decodes glyphs -> raw bytes AT COMPILE
122+
TIME (zero runtime cost; bytes baked into the BEAM). `"""` heredoc works since a
123+
literal `"` never appears in the payload.
124+
- [x] elixir/lib/printable_binary.ex — `defmacro sigil_PB({:"<<>>",_,[str]},_)` +
125+
`decode/1` (whitespace-tolerant). Map parsed from character_map.txt at compile
126+
time via @external_resource (single source of truth; same parse as all impls).
127+
- [x] elixir/test/printable_binary_test.exs — 5 tests + 1 doctest (passthrough,
128+
control glyph byte0=·, whitespace-ignored heredoc, all-256 round-trip, raise).
129+
- [x] flake check `test-elixir`: `mix test` + MFIC cross-impl guard — Zig CLI is the
130+
INDEPENDENT encoder oracle; Elixir decode/1 must reproduce originals over
131+
all-256 single bytes + 8 KiB random (2/3-byte glyph boundaries). Green.
132+
- [x] elixir/_build gitignored (swept-in artifacts moved out, not committed).

elixir/lib/printable_binary.ex

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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

elixir/mix.exs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule PrintableBinary.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :printable_binary,
7+
version: "0.1.0",
8+
elixir: "~> 1.15",
9+
description: "Compile-time ~PB sigil: printable-binary glyphs -> raw binary",
10+
deps: []
11+
]
12+
end
13+
14+
def application, do: []
15+
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
defmodule PrintableBinaryTest do
2+
use ExUnit.Case, async: true
3+
import PrintableBinary
4+
doctest PrintableBinary
5+
6+
test "~PB decodes passthrough ASCII to itself" do
7+
assert ~PB"AB" == "AB"
8+
end
9+
10+
test "~PB decodes control-char glyphs (byte 0 -> ·)" do
11+
assert PrintableBinary.decode("·") == <<0>>
12+
end
13+
14+
test "whitespace inside the sigil is ignored (heredoc/wrapping-safe)" do
15+
assert ~PB"A B" == "AB"
16+
assert ~PB"""
17+
A
18+
B
19+
""" == "AB"
20+
end
21+
22+
test "decode/1 round-trips all 256 single-byte glyphs" do
23+
# Build the inverse (byte -> glyph) from the same source, then decode each glyph.
24+
byte_to_glyph =
25+
Path.expand("../../character_map.txt", __DIR__)
26+
|> File.read!()
27+
|> String.split("\n")
28+
|> Enum.map(&String.trim_trailing(&1, "\r"))
29+
|> Enum.reject(&(&1 == "" or String.starts_with?(&1, "##")))
30+
|> Enum.map(&(&1 |> String.split() |> hd()))
31+
32+
assert length(byte_to_glyph) == 256
33+
34+
for {glyph, byte} <- Enum.with_index(byte_to_glyph) do
35+
assert PrintableBinary.decode(glyph) == <<byte>>, "byte #{byte} glyph #{inspect(glyph)}"
36+
end
37+
end
38+
39+
test "unrecognized glyph raises" do
40+
assert_raise ArgumentError, fn -> PrintableBinary.decode("🚫") end
41+
end
42+
end

elixir/test/test_helper.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ExUnit.start()

flake.nix

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@
208208
rustc
209209
clippy
210210
rustfmt
211+
212+
# Elixir/Erlang (the ~PB compile-time sigil demo)
213+
elixir
211214
] ++ lib.optionals stdenv.isDarwin [ lldb ]
212215
++ lib.optionals (!stdenv.isDarwin) [ gdb ]
213216
++ linuxOnly;
@@ -438,6 +441,37 @@
438441
installPhase = "mkdir -p $out && touch $out/passed";
439442
};
440443

444+
# ~PB compile-time sigil (Elixir). Runs the crate's own unit + doctest
445+
# suite, then the MFIC cross-impl guard: the Zig CLI is the INDEPENDENT
446+
# encoder oracle (Elixir did not write it), so Elixir decode/1 must
447+
# reproduce the original bytes for all 256 single bytes AND an 8 KiB
448+
# random multi-byte stream (exercises 2/3-byte glyph boundaries).
449+
test-elixir = pkgs.stdenv.mkDerivation {
450+
name = "test-elixir";
451+
src = ./.;
452+
nativeBuildInputs = with pkgs; [ elixir zig ];
453+
buildPhase = ''
454+
export HOME=$TMPDIR
455+
export MIX_HOME=$TMPDIR/mix
456+
export HEX_HOME=$TMPDIR/hex
457+
( cd elixir && mix test --no-deps-check )
458+
zig build
459+
for i in $(seq 0 255); do printf "\\$(printf '%03o' "$i")"; done > allbytes
460+
head -c 8192 /dev/urandom > rand.bin
461+
PRINTABLE_BINARY_MUTE_STATS=1 ./zig-out/bin/printable-binary-zig < allbytes > z_all.out
462+
PRINTABLE_BINARY_MUTE_STATS=1 ./zig-out/bin/printable-binary-zig < rand.bin > z_rand.out
463+
( cd elixir && mix run --no-start -e '
464+
for {enc, orig} <- [{"../z_all.out", "../allbytes"}, {"../z_rand.out", "../rand.bin"}] do
465+
got = PrintableBinary.decode(File.read!(enc))
466+
exp = File.read!(orig)
467+
if got != exp, do: raise "Elixir decode(Zig encode) mismatch for #{enc}"
468+
end
469+
IO.puts("Elixir decode(Zig encode) == original for all-256 + 8KiB random")
470+
' )
471+
'';
472+
installPhase = "mkdir -p $out && touch $out/passed";
473+
};
474+
441475
# Dogfood the C FFI boundary: build the C FFI CLI against the Zig static
442476
# lib and round-trip through it (encode/decode + hexlike).
443477
test-ffi-cli = pkgs.stdenv.mkDerivation {

0 commit comments

Comments
 (0)