feat(bitfield): add bitfield command engine
This commit is contained in:
1 parent
28a19cb4a9
commit
c501aba216
1 file changed
+94
@@ -0,0 +1,94 @@
|
||||
defmodule Excache.Bitfield do
|
||||
@moduledoc false
|
||||
|
||||
import Bitwise
|
||||
|
||||
def run(binary, operations) do
|
||||
{finalBinary, reversed} =
|
||||
Enum.reduce(operations, {binary, []}, fn operation, {bin, results} ->
|
||||
{updated, result} = applyOne(bin, operation)
|
||||
{updated, [result | results]}
|
||||
end)
|
||||
|
||||
{finalBinary, Enum.reverse(reversed)}
|
||||
end
|
||||
|
||||
defp applyOne(binary, {:get, signed, bits, offset}) do
|
||||
{binary, interpret(readBits(binary, offset, bits), signed, bits)}
|
||||
end
|
||||
|
||||
defp applyOne(binary, {:set, signed, bits, offset, value, overflow}) do
|
||||
old = interpret(readBits(binary, offset, bits), signed, bits)
|
||||
|
||||
case clamp(value, signed, bits, overflow) do
|
||||
:fail -> {binary, nil}
|
||||
stored -> {writeBits(binary, offset, bits, stored), old}
|
||||
end
|
||||
end
|
||||
|
||||
defp applyOne(binary, {:incrby, signed, bits, offset, delta, overflow}) do
|
||||
old = interpret(readBits(binary, offset, bits), signed, bits)
|
||||
candidate = old + delta
|
||||
|
||||
case clamp(candidate, signed, bits, overflow) do
|
||||
:fail ->
|
||||
{binary, nil}
|
||||
|
||||
stored ->
|
||||
{writeBits(binary, offset, bits, stored), interpret(rawOf(stored, bits), signed, bits)}
|
||||
end
|
||||
end
|
||||
|
||||
defp readBits(binary, offset, bits) do
|
||||
padded = padToBits(binary, offset + bits)
|
||||
<<_::size(offset), raw::size(bits), _::bitstring>> = padded
|
||||
raw
|
||||
end
|
||||
|
||||
defp writeBits(binary, offset, bits, value) do
|
||||
padded = padToBits(binary, offset + bits)
|
||||
raw = rawOf(value, bits)
|
||||
<<head::size(offset), _::size(bits), tail::bitstring>> = padded
|
||||
<<head::size(offset), raw::size(bits), tail::bitstring>>
|
||||
end
|
||||
|
||||
defp padToBits(binary, neededBits) do
|
||||
neededBytes = div(neededBits + 7, 8)
|
||||
missing = neededBytes - byte_size(binary)
|
||||
if missing > 0, do: binary <> :binary.copy(<<0>>, missing), else: binary
|
||||
end
|
||||
|
||||
defp interpret(raw, false, _bits), do: raw
|
||||
|
||||
defp interpret(raw, true, bits) do
|
||||
if raw >= 1 <<< (bits - 1), do: raw - (1 <<< bits), else: raw
|
||||
end
|
||||
|
||||
defp rawOf(value, bits), do: value &&& (1 <<< bits) - 1
|
||||
|
||||
defp clamp(value, signed, bits, overflow) do
|
||||
{low, high} = bounds(signed, bits)
|
||||
|
||||
cond do
|
||||
value >= low and value <= high ->
|
||||
value
|
||||
|
||||
overflow == :wrap ->
|
||||
wrap(value, signed, bits)
|
||||
|
||||
overflow == :sat ->
|
||||
if value < low, do: low, else: high
|
||||
|
||||
overflow == :fail ->
|
||||
:fail
|
||||
end
|
||||
end
|
||||
|
||||
defp wrap(value, signed, bits) do
|
||||
raw = rawOf(value, bits)
|
||||
interpret(raw, signed, bits)
|
||||
end
|
||||
|
||||
defp bounds(false, bits), do: {0, (1 <<< bits) - 1}
|
||||
defp bounds(true, bits), do: {-(1 <<< (bits - 1)), (1 <<< (bits - 1)) - 1}
|
||||
end
|
||||
Reference in new issue
Block a user