defmodule Dmp.DebugUtils do
@moduledoc """
Utilities for debugging bitarrays.
"""
use Bitwise, only_operators: true
@doc """
Formats an alphabet bitarray into a list of lines, showing binary values.
* `s` - The alphabet as returned by `Dmp.Match.alphabet/1`.
* `pattern` - The pattern that is associated with the alphabet.
## Examples
iex> DebugUtils.debug_alphabet(%{?a => 5, ?b => 2}, "aba")
[
"alphabet: a b a",
" a 5: 1 0 1",
" b 2: 0 1 0"
]
"""
@spec debug_alphabet(Dmp.Match.alpha(), String.t()) :: [String.t()]
def debug_alphabet(s, pattern) do
pattern_length = String.length(pattern)
int_len = value_size(pattern_length)
data =
String.codepoints(pattern)
|> Enum.sort()
|> Enum.dedup()
|> Enum.map(fn ch -> alphabet_line(ch, s, pattern_length, int_len) end)
[alphabet_header(pattern, int_len) | data]
end
defp alphabet_header(pattern, int_len) do
label = String.pad_leading("alphabet:", int_len + 7)
line = [label | String.codepoints(pattern)]
Enum.join(line, " ")
end
defp alphabet_line(ch, s, pattern_length, int_len) do
ord = String.to_charlist(ch) |> List.first()
value = Map.get(s, ord, 0)
valstr = format_int(value, int_len)
bits = bitmap_to_list(value, pattern_length)
line = [" ", ch, valstr <> ":"] ++ bits
Enum.join(line, " ")
end
@doc """
Formats the `rd` bitarray into a list of lines, showing binary values.
* `rd` - The bitarrary.
* `text` - The text associated with `rd`.
* `pattern` - The pattern associated with `rd`.
* `d` - The error level.
* `start` - The lowest index that has been calculated. Lines below this
index will be marked with an asterisk.
* `best_loc` - The index in the text where the best match has been found.
The line at this index will be marked with an "@"-sign.
## Examples
iex> DebugUtils.debug_rd(%{1 => 5, 2 => 7, -1 => 3}, "abc", "add", 0, 1, 2)
[
" rd_j^0: a d d",
" 0* _ 0: 0 0 0",
" 1 a 5: 1 0 1",
" 2 b 7: 1 1 1",
" 3@ c 0: 0 0 0",
" 4 _ 0: 0 0 0"
]
"""
@spec debug_rd(
Dmp.Match.bitap_array(),
String.t(),
String.t(),
non_neg_integer(),
non_neg_integer(),
integer()
) :: [String.t()]
def debug_rd(rd, text, pattern, d, start \\ 0, best_loc \\ -1) do
pattern_length = String.length(pattern)
int_len = value_size(pattern_length)
rd_size = max(String.length(text) + 2, Map.fetch!(rd, -1))
data =
Enum.map(0..(rd_size - 1), fn j ->
ch =
if j == 0 do
nil
else
String.at(text, j - 1)
end
rd_j_line(ch, j, rd, pattern_length, int_len, start, best_loc)
end)
[rd_header(d, pattern, int_len) | data]
end
defp rd_header(d, pattern, int_len) do
label = "rd_j^#{d}:" |> String.pad_leading(int_len + 7)
line = [label | String.codepoints(pattern)]
Enum.join(line, " ")
end
defp rd_j_line(nil, j, rd, pattern_length, int_len, start, best_loc) do
rd_j_line("_", j, rd, pattern_length, int_len, start, best_loc)
end
defp rd_j_line(ch, j, rd, pattern_length, int_len, start, best_loc) do
value = Map.get(rd, j, 0)
valstr = format_int(value, int_len)
jstr = to_string(j) |> String.pad_leading(2)
jstr =
cond do
best_loc != -1 && j - 1 == best_loc ->
jstr <> "@"
j < start ->
jstr <> "*"
true ->
jstr <> " "
end
bits = bitmap_to_list(value, pattern_length)
line = [jstr, ch, valstr <> ":"] ++ bits
Enum.join(line, " ")
end
defp value_size(pattern_length) do
largest_bitmap = (1 <<< pattern_length) - 1
len = to_string(largest_bitmap) |> String.length()
min(14, max(len, 2))
end
defp format_int(value, max_len) do
valstr = to_string(value) |> String.pad_leading(max_len)
if String.length(valstr) > max_len do
String.pad_leading("<>", max_len)
else
valstr
end
end
@doc """
Returns a list of "codepoints" (single-character strings)
showing the base-2 value of `value`.
A minimum of `padding` elements are returned.
"""
@spec bitmap_to_list(non_neg_integer(), non_neg_integer()) :: [String.t()]
def bitmap_to_list(value, padding \\ 0) do
encode_bit_loop(value, "")
|> String.pad_leading(padding, "0")
|> String.codepoints()
end
defp encode_bit_loop(v, acc) do
acc =
if (v &&& 1) == 0 do
"0" <> acc
else
"1" <> acc
end
v = v >>> 1
if v == 0 do
acc
else
encode_bit_loop(v, acc)
end
end
end