summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorJacob Abel <jacobabel@nullpo.dev>2022-07-08 19:37:45 -0400
committerJacob Abel <jacobabel@nullpo.dev>2022-10-23 17:50:24 -0400
commit39a4ab78a1245eb45d333fc14ec56f3a8f045986 (patch)
treefd264453068cd9be8e1d9445d3105954ac0d5173 /lib
parent3d196a5f2a72595b14c439a9b4aba7737c0f1ebe (diff)
lib/strings: Refactor toInt into toInt and toIntBase10
Diffstat (limited to 'lib')
-rw-r--r--lib/default.nix2
-rw-r--r--lib/strings.nix54
-rw-r--r--lib/tests/misc.nix60
-rwxr-xr-xlib/tests/modules.sh2
4 files changed, 97 insertions, 21 deletions
diff --git a/lib/default.nix b/lib/default.nix
index 0c0e2d5e10217..8bb06954518b9 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -103,7 +103,7 @@ let
       getName getVersion
       nameFromURL enableFeature enableFeatureAs withFeature
       withFeatureAs fixedWidthString fixedWidthNumber isStorePath
-      toInt readPathsFromFile fileContents;
+      toInt toIntBase10 readPathsFromFile fileContents;
     inherit (self.stringsWithDeps) textClosureList textClosureMap
       noDepEntry fullDepEntry packEntry stringAfter;
     inherit (self.customisation) overrideDerivation makeOverridable
diff --git a/lib/strings.nix b/lib/strings.nix
index c6269e755e2a1..368ec786d6703 100644
--- a/lib/strings.nix
+++ b/lib/strings.nix
@@ -783,26 +783,74 @@ rec {
     else
       false;
 
-  /* Parse a string as an int.
+  /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
+  ambiguity between zero-padded and octal numbers.
 
      Type: string -> int
 
      Example:
+
        toInt "1337"
        => 1337
+
        toInt "-4"
        => -4
+
        toInt " 123 "
        => 123
+
        toInt "00024"
-       => 24
+       => error: [json.exception.parse_error.101] parse error at line 1, column 2: syntax error
+       while parsing value - unexpected number literal; expected end of input
+
        toInt "3.14"
        => error: floating point JSON numbers are not supported
   */
   # Obviously, it is a bit hacky to use fromJSON this way.
   toInt = str:
     let
-      # RegEx: Match any leading whitespace, then any zero padding, and capture any remaining
+      # RegEx: Match any leading whitespace, then any digits, and finally match any trailing
+      # whitespace.
+      strippedInput = match "[[:space:]]*([[:digit:]]+)[[:space:]]*" str;
+
+      # RegEx: Match any leading whitespace, then a leading '0', then at least one digit following
+      # after, and finally match any trailing whitespace.
+      isLeadingZero = match "[[:space:]]*0[[:digit:]]+[[:space:]]*" str == [];
+
+      # Attempt to parse input
+      parsedInput = fromJSON (elemAt strippedInput 0);
+    in
+      if isLeadingZero
+      then throw "Ambiguity in ${str} between octal and zero padded integer."
+      else if strippedInput != null && isInt parsedInput
+      then parsedInput
+      else throw "Could not convert ${str} to int.";
+
+
+  /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
+
+     Type: string -> int
+
+     Example:
+       toIntBase10 "1337"
+       => 1337
+
+       toIntBase10 "-4"
+       => -4
+
+       toIntBase10 " 123 "
+       => 123
+
+       toIntBase10 "00024"
+       => 24
+
+       toIntBase10 "3.14"
+       => error: floating point JSON numbers are not supported
+  */
+  # Obviously, it is a bit hacky to use fromJSON this way.
+  toIntBase10 = str:
+    let
+      # RegEx: Match any leading whitespace, then match any zero padding, capture any remaining
       # digits after that, and finally match any trailing whitespace.
       strippedInput = match "[[:space:]]*0*([[:digit:]]+)[[:space:]]*" str;
 
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index 97d53026c644c..4bfc8bb876994 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -339,20 +339,6 @@ runTests {
     (0 == toInt " 0")
     (0 == toInt "0 ")
     (0 == toInt " 0 ")
-    # Zero Padding
-    (123 == toInt "0123")
-    (123 == toInt "0000123")
-    (0 == toInt "000000")
-    # Whitespace and Zero Padding
-    (123 == toInt " 0123")
-    (123 == toInt "0123 ")
-    (123 == toInt " 0123 ")
-    (123 == toInt " 0000123")
-    (123 == toInt "0000123 ")
-    (123 == toInt " 0000123 ")
-    (0 == toInt " 000000")
-    (0 == toInt "000000 ")
-    (0 == toInt " 000000 ")
   ];
 
   testToIntFails = testAllTrue [
@@ -360,10 +346,52 @@ runTests {
     ( builtins.tryEval (toInt "123 123") == { success = false; value = false; } )
     ( builtins.tryEval (toInt "0 123") == { success = false; value = false; } )
     ( builtins.tryEval (toInt " 0d ") == { success = false; value = false; } )
+    ( builtins.tryEval (toInt "00") == { success = false; value = false; } )
+    ( builtins.tryEval (toInt "01") == { success = false; value = false; } )
+    ( builtins.tryEval (toInt "002") == { success = false; value = false; } )
+    ( builtins.tryEval (toInt " 002 ") == { success = false; value = false; } )
     ( builtins.tryEval (toInt " foo ") == { success = false; value = false; } )
     ( builtins.tryEval (toInt " foo 123 ") == { success = false; value = false; } )
-    ( builtins.tryEval (toInt " foo 00123 ") == { success = false; value = false; } )
-    ( builtins.tryEval (toInt " foo00123 ") == { success = false; value = false; } )
+    ( builtins.tryEval (toInt " foo123 ") == { success = false; value = false; } )
+  ];
+
+  testToIntBase10 = testAllTrue [
+    # Naive
+    (123 == toIntBase10 "123")
+    (0 == toIntBase10 "0")
+    # Whitespace Padding
+    (123 == toIntBase10 " 123")
+    (123 == toIntBase10 "123 ")
+    (123 == toIntBase10 " 123 ")
+    (123 == toIntBase10 "   123   ")
+    (0 == toIntBase10 " 0")
+    (0 == toIntBase10 "0 ")
+    (0 == toIntBase10 " 0 ")
+    # Zero Padding
+    (123 == toIntBase10 "0123")
+    (123 == toIntBase10 "0000123")
+    (0 == toIntBase10 "000000")
+    # Whitespace and Zero Padding
+    (123 == toIntBase10 " 0123")
+    (123 == toIntBase10 "0123 ")
+    (123 == toIntBase10 " 0123 ")
+    (123 == toIntBase10 " 0000123")
+    (123 == toIntBase10 "0000123 ")
+    (123 == toIntBase10 " 0000123 ")
+    (0 == toIntBase10 " 000000")
+    (0 == toIntBase10 "000000 ")
+    (0 == toIntBase10 " 000000 ")
+  ];
+
+  testToIntBase10Fails = testAllTrue [
+    ( builtins.tryEval (toIntBase10 "") == { success = false; value = false; } )
+    ( builtins.tryEval (toIntBase10 "123 123") == { success = false; value = false; } )
+    ( builtins.tryEval (toIntBase10 "0 123") == { success = false; value = false; } )
+    ( builtins.tryEval (toIntBase10 " 0d ") == { success = false; value = false; } )
+    ( builtins.tryEval (toIntBase10 " foo ") == { success = false; value = false; } )
+    ( builtins.tryEval (toIntBase10 " foo 123 ") == { success = false; value = false; } )
+    ( builtins.tryEval (toIntBase10 " foo 00123 ") == { success = false; value = false; } )
+    ( builtins.tryEval (toIntBase10 " foo00123 ") == { success = false; value = false; } )
   ];
 
 # LISTS
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index f6298297d1367..92c28369ed5cd 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -162,7 +162,7 @@ checkConfigError 'A definition for option .* is not.*string or signed integer co
 # Check coerced value with unsound coercion
 checkConfigOutput '^12$' config.value ./declare-coerced-value-unsound.nix
 checkConfigError 'A definition for option .* is not of type .*. Definition values:\n\s*- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
-checkConfigError 'Could not convert .* to int.' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
+checkConfigError 'Could not convert .* to int' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
 
 # Check mkAliasOptionModule.
 checkConfigOutput '^true$' config.enable ./alias-with-priority.nix