Skip to content

Commit

Permalink
lib/int-representations: init
Browse files Browse the repository at this point in the history
Signed-off-by: lucasew <lucas59356@gmail.com>
  • Loading branch information
lucasew committed Nov 10, 2023
1 parent 558d433 commit 07f4343
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 16 deletions.
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

0 comments on commit 07f4343

Please sign in to comment.