Add non-assertion parser for STUint.
This commit is contained in:
parent
711cda4456
commit
923ee80009
|
@ -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
|
||||||
|
|
100
stint/io.nim
100
stint/io.nim
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue