Add non-assertion parser for STUint.

This commit is contained in:
cheatfate 2023-10-17 00:26:18 +03:00
parent 711cda4456
commit 923ee80009
No known key found for this signature in database
GPG Key ID: 46ADD633A7201F95
2 changed files with 95 additions and 22 deletions

View File

@ -8,6 +8,7 @@ skipDirs = @["tests", "benchmarks"]
# TODO test only requirements don't work: https://github.com/nim-lang/nimble/issues/482 # TODO test only requirements don't work: https://github.com/nim-lang/nimble/issues/482
requires "nim >= 1.6.12", requires "nim >= 1.6.12",
"results",
"stew" "stew"
let nimc = getEnv("NIMC", "nim") # Which nim compiler to use let nimc = getEnv("NIMC", "nim") # Which nim compiler to use

View File

@ -10,11 +10,14 @@
import import
# Standard library # Standard library
std/[typetraits, algorithm, hashes], std/[typetraits, algorithm, hashes],
# External libraries
results,
# Internal # Internal
./private/datatypes, ./private/datatypes,
./uintops, ./endians2 ./uintops, ./endians2
from stew/byteutils import toHex from stew/byteutils import toHex
export results
# Helpers # Helpers
# -------------------------------------------------------- # --------------------------------------------------------
@ -198,29 +201,48 @@ func readHexChar(c: char): int8 {.inline.}=
else: else:
raise newException(ValueError, $c & "is not a hexadecimal character") raise newException(ValueError, $c & "is not a hexadecimal character")
func skipPrefixes(current_idx: var int, str: string, radix: range[2..16]) {.inline.} = func readStrictHexChar(c: char): Result[int8, cstring] {.raises: [], inline.} =
## Converts an hex char to an int
case c
of '0'..'9': ok(int8 ord(c) - ord('0'))
of 'a'..'f': ok(int8 ord(c) - ord('a') + 10)
of 'A'..'F': ok(int8 ord(c) - ord('A') + 10)
else:
err("Invalid hexadecimal character encountered!")
func skipPrefixes(str: string,
radix: range[2..16]): Result[int, cstring] {.
raises: [], inline.} =
## Returns the index of the first meaningful char in `hexStr` by skipping ## Returns the index of the first meaningful char in `hexStr` by skipping
## "0x" prefix ## "0x" prefix
if len(str) < 2:
return ok(0)
if str.len < 2:
return return
doAssert current_idx == 0, "skipPrefixes only works for prefixes (position 0 and 1 of the string)"
if str[0] == '0': if str[0] == '0':
if str[1] in {'x', 'X'}: if str[1] in {'x', 'X'}:
doAssert radix == 16, "Parsing mismatch, 0x prefix is only valid for a hexadecimal number (base 16)" if radix != 16:
current_idx = 2 return err("Parsing mismatch, 0x prefix is only valid for a " &
"hexadecimal number (base 16)")
ok(2)
elif str[1] in {'o', 'O'}: elif str[1] in {'o', 'O'}:
doAssert radix == 8, "Parsing mismatch, 0o prefix is only valid for an octal number (base 8)" if radix != 8:
current_idx = 2 return err("Parsing mismatch, 0o prefix is only valid for an " &
"octal number (base 8)")
ok(2)
elif str[1] in {'b', 'B'}: elif str[1] in {'b', 'B'}:
if radix == 2: if radix == 2:
current_idx = 2 ok(2)
elif radix == 16: elif radix == 16:
# allow something like "0bcdef12345" which is a valid hex # allow something like "0bcdef12345" which is a valid hex
current_idx = 0 ok(0)
else: else:
doAssert false, "Parsing mismatch, 0b prefix is only valid for a binary number (base 2), or hex number" err("Parsing mismatch, 0b prefix is only valid for a binary number " &
"(base 2), or hex number")
else:
ok(0)
else:
ok(0)
func nextNonBlank(current_idx: var int, s: string) {.inline.} = func nextNonBlank(current_idx: var int, s: string) {.inline.} =
## Move the current index, skipping white spaces and "_" characters. ## Move the current index, skipping white spaces and "_" characters.
@ -236,6 +258,47 @@ func readDecChar(c: range['0'..'9']): int {.inline.}=
# specialization without branching for base <= 10. # specialization without branching for base <= 10.
ord(c) - ord('0') ord(c) - ord('0')
func readStrictDecChar(c: char): Result[int8, cstring] {.raises: [], inline.} =
case c
of '0'..'9': ok(int8 ord(c) - ord('0'))
else:
err("Invalid decimal character encountered!")
func strictParse*[bits: static[int]](input: string,
T: typedesc[StUint[bits]],
radix: static[uint8] = 10
): Result[T, cstring] {.raises: [].} =
var res: T
static: doAssert (radix >= 2) and (radix <= 16),
"Only base from 2..16 are supported"
const
base = radix.uint8.stuint(bits)
zero = 0.uint8.stuint(256)
var currentIndex =
block:
let res = skipPrefixes(input, radix)
if res.isErr():
return err(res.error)
res.get()
while currentIndex < len(input):
let value =
when radix <= 10:
? readStrictDecChar(input[currentIndex])
else:
? readStrictHexChar(input[currentIndex])
let mres = res * base
if (res != zero) and (mres div base != res):
return err("Overflow error")
let ares = mres + value.stuint(bits)
if ares < mres:
return err("Overflow error")
res = ares
inc(currentIndex)
ok(res)
func parse*[bits: static[int]](input: string, func parse*[bits: static[int]](input: string,
T: typedesc[StUint[bits]], T: typedesc[StUint[bits]],
radix: static[uint8] = 10): T = radix: static[uint8] = 10): T =
@ -248,8 +311,12 @@ func parse*[bits: static[int]](input: string,
# and be much faster # and be much faster
const base = radix.uint8.stuint(bits) const base = radix.uint8.stuint(bits)
var curr = 0 # Current index in the string var curr =
skipPrefixes(curr, input, radix) block:
let res = skipPrefixes(input, radix)
if res.isErr():
raiseAssert $res.error
res.get()
while curr < input.len: while curr < input.len:
# TODO: overflow detection # TODO: overflow detection
@ -282,7 +349,12 @@ func parse*[bits: static[int]](input: string,
isNeg = true isNeg = true
inc curr inc curr
else: else:
skipPrefixes(curr, input, radix) curr =
block:
let res = skipPrefixes(input, radix)
if res.isErr():
raiseAssert $res.error
res.get()
while curr < input.len: while curr < input.len:
# TODO: overflow detection # TODO: overflow detection