Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib/bit-representation: init #258834

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ let
# misc
asserts = callLibs ./asserts.nix;
debug = callLibs ./debug.nix;
int-representations = callLibs ./int-representations.nix;
misc = callLibs ./deprecated.nix;

# domain-specific
Expand Down Expand Up @@ -75,7 +76,7 @@ let
info showWarnings nixpkgsVersion version isInOldestRelease
mod compare splitByAndCompare
functionArgs setFunctionArgs isFunction toFunction
toHexString toBaseDigits inPureEvalMode;
mkLookupTable fromHexString toHexString toBaseDigits inPureEvalMode;
inherit (self.fixedPoints) fix fix' converge extends composeExtensions
composeManyExtensions makeExtensible makeExtensibleWithCustomName;
inherit (self.attrsets) attrByPath hasAttrByPath setAttrByPath
Expand Down
78 changes: 78 additions & 0 deletions lib/int-representations.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* Collection of functions for bit representation conversions such as binary,
octal, decimal and hexadecimal.
These functions works by using a bit mapping, a attrset that maps each
character to a numerical value. The base of the numeric system is the length
of the keys of this attrset.
*/

{ lib }:

{
/* Default bit mappings for the most common bases */
mappings = { # maps characters to values
bin = [ "0" "1"];
oct = [ "0" "1" "2" "3" "4" "5" "6" "7" ];
dec = [ "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" ];
hex = [ "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F" ];
};

/* Convert an integer to the string representation following the mapping.
Type: toStringRepresentation :: attrs -> int -> string
Example:
toStringRepresentation bitMappings.hex 255
=> "FF"
*/
toStringRepresentation =
# List of symbols of the base alphabet. Base number is inferred from the size.
mapping:
# Integer to be converted
number:
let
mappingBase = lib.length mapping;
digits = lib.toBaseDigits mappingBase number;

chars = map (digit: lib.elemAt mapping digit) digits;

in lib.concatStringsSep "" chars;

/* Convert a string representation to integer using the mapping.
Note that this function doesn't support prefixes such as 0x for
hexadecimal.
Type: fromStringRepresentation :: attrs -> string -> int
Example:
fromStringRepresentation bitMappings.hex "ff"
=> 255
*/
fromStringRepresentation =
# List of symbols of the base alphabet. Base number is inferred from the size.
mapping:
# String representation to be converted
repr:
let
throwInvalidChar = throw "invalid symbol in '${repr}', all valid symbols in this mapping: ${lib.concatStringsSep ", " mapping}";

lookupTable = lib.mkLookupTable mapping;

# which base?
mappingMultiplier = lib.length mapping;

chars = lib.splitString "" repr;
nonEmptyChars = (lib.filter (c: c != "")) chars;

getCharValue = c: lookupTable.${c} or throwInvalidChar;

charValues = map getCharValue nonEmptyChars;
reversedChars = lib.reverseList charValues;

convertChars = charList:
if lib.length charList == 0
then 0
else (mappingMultiplier * (convertChars (lib.tail charList))) + (lib.head charList);
in convertChars reversedChars;
}
73 changes: 73 additions & 0 deletions lib/tests/misc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,31 @@ runTests {
expected = "FA";
};

testFromHexString = {
expr = fromHexString "FA";
expected = 250;
};

testFromHexStringHashedSHA512 = {
expr = fromHexString (toUpper (builtins.hashString "sha512" "test"));
expected = -8183225288127633153; # the overflow is expected
};

testFromHexStringHashedSHA256 = {
expr = fromHexString (toUpper (builtins.hashString "sha256" "test"));
expected = -3360410906529887736; # the overflow is expected
};

testFromHexStringHashedSHA1 = {
expr = fromHexString (toUpper (builtins.hashString "sha1" "test"));
expected = -3201521091500590125; # the overflow is expected
};

testFromHexStringHashedMD5 = {
expr = fromHexString (toUpper (builtins.hashString "md5" "test"));
expected = -3828536308030524170; # the overflow is expected
};

testToBaseDigits = {
expr = toBaseDigits 2 6;
expected = [ 1 1 0 ];
Expand Down Expand Up @@ -1939,4 +1964,52 @@ runTests {
testGetExe'FailureSecondArg = testingThrow (
getExe' { type = "derivation"; } "dir/executable"
);

# BIT REPRESENTATION
testIntRepresentationsParseZero = {
expr = mapAttrs (k: v: int-representations.fromStringRepresentation v "0") int-representations.mappings;
expected = mapAttrs (k: v: 0) int-representations.mappings;
};

testIntRepresentationsStringifyMaxPlusOne = {
expr = int-representations.toStringRepresentation int-representations.mappings.hex 16;
expected = "10";
};

testIntRepresentationsStringifyExample = {
expr = int-representations.toStringRepresentation int-representations.mappings.hex 255;
expected = "FF";
};

testIntRepresentationsParseExample = {
expr = int-representations.fromStringRepresentation int-representations.mappings.hex "FF";
expected = 255;
};

# python code used to generate these numbers:
# from random import randint; x = randint(0, 999999); hx = hex(x); print(x, hx)
testIntRepresentationsStringifyFromPython-1 = {
expr = int-representations.toStringRepresentation int-representations.mappings.hex 158530;
expected = "26B42";
};

testIntRepresentationsStringifyFromPython-2 = {
expr = int-representations.toStringRepresentation int-representations.mappings.hex 10374;
expected = "2886";
};

testIntRepresentationsStringifyFromPython-3 = {
expr = int-representations.toStringRepresentation int-representations.mappings.hex 741819;
expected = "B51BB";
};

testIntRepresentationStringifyCustomRepresentation = {
expr = int-representations.toStringRepresentation [ "A" "U" "C" "G" ] 420;
expected = "UCCUA";
};
testIntRepresentationParseCustomRepresentation = {
expr = int-representations.fromStringRepresentation [ "A" "U" "C" "G" ] "UCCUA";
expected = 420;
};

}
57 changes: 42 additions & 15 deletions lib/trivial.nix
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,46 @@ rec {
then v
else k: v;

/* Builds a lookup table from a list of values so
getting the index of a value becomes $O(1)$ instead
of $O(n)$
Type: mkLookupTable :: list -> attrs
Example:
mkLookupTable [ "a" "b" "c" ]
=> {a = 0; b = 1; c = 2; }
*/
mkLookupTable =
# List of values that are or can be converted to string.
mapping:
let
foldFn = a: b: a // {
"${b}" = a._index;
_index = a._index + 1;
};
folded = lib.foldl foldFn {_index = 0;} mapping;
in lib.attrsets.removeAttrs folded [ "_index" ];


/* Convert a hexadecimal representation to a positive integer
Type: fromHexString :: string -> int
Example:
fromHexString "0"
=> 0
fromHexString "10"
=> 16
fromHexString "FA"
=> 250
*/
fromHexString = representation:
lib.int-representations.fromStringRepresentation
lib.int-representations.mappings.hex representation;

/* Convert the given positive integer to a string of its hexadecimal
representation. For example:
Expand All @@ -477,21 +517,8 @@ rec {
toHexString 250 => "FA"
*/
toHexString = i:
let
toHexDigit = d:
if d < 10
then toString d
else
{
"10" = "A";
"11" = "B";
"12" = "C";
"13" = "D";
"14" = "E";
"15" = "F";
}.${toString d};
in
lib.concatMapStrings toHexDigit (toBaseDigits 16 i);
lib.int-representations.toStringRepresentation
lib.int-representations.mappings.hex i;

/* `toBaseDigits base i` converts the positive integer i to a list of its
digits in the given base. For example:
Expand Down