about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJacob Abel <jacobabel@nullpo.dev>2022-07-09 20:12:31 -0400
committerJacob Abel <jacobabel@nullpo.dev>2022-10-23 17:50:24 -0400
commited71173841618bd4c69f40d07fb467ccabc5db0b (patch)
tree4532a1f7bc9459c07badc3e91e80bf5260d8832f
parent88b18dcf445a1be963a6bd2f9e8c075edd668f71 (diff)
lib/strings: Update docs and restructured code to improve readability of toInt and toIntBase10.
-rw-r--r--lib/strings.nix58
-rw-r--r--lib/tests/misc.nix4
-rwxr-xr-xlib/tests/modules.sh2
3 files changed, 42 insertions, 22 deletions
diff --git a/lib/strings.nix b/lib/strings.nix
index 5c5507b597b6a..298d3b2af0838 100644
--- a/lib/strings.nix
+++ b/lib/strings.nix
@@ -784,7 +784,7 @@ rec {
       false;
 
   /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
-  ambiguity between zero-padded and octal numbers.
+  ambiguity between zero-padded and octal numbers. See toIntBase10.
 
      Type: string -> int
 
@@ -805,25 +805,35 @@ rec {
        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 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 == [];
+      # RegEx: Match a leading '0' then one or more digits.
+      isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == [];
 
       # Attempt to parse input
-      parsedInput = fromJSON (elemAt strippedInput 0);
+      parsedInput = fromJSON (head strippedInput);
+
+      generalError = "toInt: Could not convert ${escapeNixString str} to int.";
+
+      octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}"
+      + " between octal and zero padded integer.";
+
     in
-      if isLeadingZero
-      then throw "Ambiguity in interpretation of ${str} between octal and zero padded integer."
-      else if strippedInput != null && isInt parsedInput
-      then parsedInput
-      else throw "Could not convert ${str} to int.";
+      # Error on presence of non digit characters.
+      if strippedInput == null
+      then throw generalError
+      # Error on presence of leading zero/octal ambiguity.
+      else if isLeadingZero
+      then throw octalAmbigError
+      # Error if parse function fails.
+      else if !isInt parsedInput
+      then throw generalError
+      # Return result.
+      else parsedInput;
 
 
   /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
@@ -846,26 +856,32 @@ rec {
        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;
 
-      # RegEx: Match any leading whitespace, at least one '0', and any trailing whitespace.
-      isZero = match "[[:space:]]*0+[[:space:]]*" str == [];
+      # RegEx: Match at least one '0'.
+      isZero = match "0+" (head strippedInput) == [];
 
       # Attempt to parse input
-      parsedInput = fromJSON (elemAt strippedInput 0);
+      parsedInput = fromJSON (head strippedInput);
+
+      generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
+
     in
-      # Value is zero
-      if isZero
+      # Error on presence of non digit characters.
+      if strippedInput == null
+      then throw generalError
+      # In the special case zero-padded zero (00000), return early.
+      else if isZero
       then 0
-      else
-      if strippedInput != null && isInt parsedInput
-      then parsedInput
-      else throw "Could not convert ${str} to int.";
+      # Error if parse function fails.
+      else if !isInt parsedInput
+      then throw generalError
+      # Return result.
+      else parsedInput;
 
   /* Read a list of paths from `file`, relative to the `rootPath`.
      Lines beginning with `#` are treated as comments and ignored.
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index 4bfc8bb876994..31c938a8ffda1 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -346,6 +346,8 @@ 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 " 1d ") == { success = false; value = false; } )
+    ( builtins.tryEval (toInt " d0 ") == { 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; } )
@@ -388,6 +390,8 @@ runTests {
     ( 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 " 1d ") == { success = false; value = false; } )
+    ( builtins.tryEval (toIntBase10 " d0 ") == { 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; } )
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index 92c28369ed5cd..c9ea674ee104a 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 'toInt: 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