diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 4ac40d8b4e..1d356dff43 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -930,6 +930,19 @@ proc toHex*[T](x: T): string = ## Shortcut for ``toHex(x, T.sizeOf * 2)`` toHex(BiggestInt(x), T.sizeOf * 2) +proc toHex*(s: string): string {.noSideEffect, rtl.} = + ## Converts a bytes string to its hexadecimal representation. + ## + ## The output is twice the input long. No prefix like + ## ``0x`` is generated. + const HexChars = "0123456789ABCDEF" + result = newString(s.len * 2) + for pos, c in s: + var n = ord(c) + result[pos * 2 + 1] = HexChars[n and 0xF] + n = n shr 4 + result[pos * 2] = HexChars[n] + proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect, rtl, extern: "nsuIntToStr".} = ## Converts `x` to its decimal representation. @@ -1012,6 +1025,43 @@ proc parseHexInt*(s: string): int {.noSideEffect, procvar, of '\0': break else: raise newException(ValueError, "invalid integer: " & s) +proc generateHexCharToValueMap(): string = + ## Generate a string to map a hex digit to uint value + result = "" + for inp in 0..255: + let ch = chr(inp) + let o = + case ch: + of '0'..'9': inp - ord('0') + of 'a'..'f': inp - ord('a') + 10 + of 'A'..'F': inp - ord('A') + 10 + else: 17 # indicates an invalid hex char + result.add chr(o) + +const hexCharToValueMap = generateHexCharToValueMap() + +proc parseHexStr*(s: string): string {.noSideEffect, procvar, + rtl, extern: "nsuParseHexStr".} = + ## Convert hex-encoded string to byte string, e.g.: + ## + ## .. code-block:: nim + ## hexToStr("00ff") == "\0\255" + ## + ## Raises ``ValueError`` for an invalid hex values. The comparison is + ## case-insensitive. + if s.len mod 2 != 0: + raise newException(ValueError, "Incorrect hex string len") + result = newString(s.len div 2) + var buf = 0 + for pos, c in s: + let val = hexCharToValueMap[ord(c)].ord + if val == 17: + raise newException(ValueError, "Invalid hex char " & repr(c)) + if pos mod 2 == 0: + buf = val + else: + result[pos div 2] = chr(val + buf shl 4) + proc parseBool*(s: string): bool = ## Parses a value into a `bool`. ## diff --git a/tests/stdlib/tstrutil.nim b/tests/stdlib/tstrutil.nim index 071dae5a77..6f78a91ac6 100644 --- a/tests/stdlib/tstrutil.nim +++ b/tests/stdlib/tstrutil.nim @@ -229,6 +229,24 @@ assert "/1/2/3".rfind('0') == -1 assert(toHex(100i16, 32) == "00000000000000000000000000000064") assert(toHex(-100i16, 32) == "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C") +assert "".parseHexStr == "" +assert "00Ff80".parseHexStr == "\0\xFF\x80" +try: + discard "00Ff8".parseHexStr + assert false, "Should raise ValueError" +except ValueError: + discard + +try: + discard "0k".parseHexStr + assert false, "Should raise ValueError" +except ValueError: + discard + +assert "".toHex == "" +assert "\x00\xFF\x80".toHex == "00FF80" +assert "0123456789abcdef".parseHexStr.toHex == "0123456789ABCDEF" + assert(' '.repeat(8)== " ") assert(" ".repeat(8) == " ") assert(spaces(8) == " ")