mirror of
https://github.com/waku-org/nwaku.git
synced 2025-01-15 17:35:45 +00:00
211 lines
6.3 KiB
Nim
211 lines
6.3 KiB
Nim
import
|
|
stew/ptrops,
|
|
inputs, outputs, buffers, async_backend, multisync,
|
|
system/formatfloat
|
|
|
|
template matchingIntType(T: type int64): type = uint64
|
|
template matchingIntType(T: type int32): type = uint32
|
|
template matchingIntType(T: type uint64): type = int64
|
|
template matchingIntType(T: type uint32): type = int32
|
|
|
|
# To reduce the produce code bloat, we will compile the integer
|
|
# handling functions only for the native type of the platform.
|
|
# Smaller int types will be automatically promoted to the native.
|
|
# On a 32-bit platforms, we'll also compile support for 64-bit types.
|
|
when sizeof(int) == sizeof(int64):
|
|
type
|
|
CompiledIntTypes = int64
|
|
PromotedIntTypes = int8|int16|int32
|
|
CompiledUIntTypes = uint64
|
|
PromotedUintTypes = uint8|uint16|uint32
|
|
else:
|
|
type
|
|
CompiledIntTypes = int|int64
|
|
PromotedIntTypes = int8|int16
|
|
CompiledUIntTypes = uint|uint64
|
|
PromotedUintTypes = uint8|uint16
|
|
|
|
# The following code implements writing numbers to a stream without going
|
|
# through Nim's `$` operator which will allocate memory.
|
|
# It's based on some speed comparisons of different methods presented here:
|
|
# http://www.zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html
|
|
|
|
const
|
|
digitsTable = block:
|
|
var s = ""
|
|
for i in 0..99:
|
|
if i < 10: s.add '0'
|
|
s.add $i
|
|
s
|
|
|
|
maxLen = ($BiggestInt.high).len + 4 # null terminator, sign
|
|
|
|
proc writeText*(s: OutputStream, x: CompiledUIntTypes) =
|
|
var
|
|
num: array[maxLen, char]
|
|
pos = num.len
|
|
|
|
template writeByteInReverse(c: char) =
|
|
dec pos
|
|
num[pos] = c
|
|
|
|
var val = x
|
|
while val > 99:
|
|
# Integer division is slow so do it for a group of two digits instead
|
|
# of for every digit. The idea comes from the talk by Alexandrescu
|
|
# "Three Optimization Tips for C++".
|
|
let base100digitIdx = (val mod 100) * 2
|
|
val = val div 100
|
|
|
|
writeByteInReverse digitsTable[base100digitIdx + 1]
|
|
writeByteInReverse digitsTable[base100digitIdx]
|
|
|
|
when true:
|
|
if val < 10:
|
|
writeByteInReverse char(ord('0') + val)
|
|
else:
|
|
let base100digitIdx = val * 2
|
|
writeByteInReverse digitsTable[base100digitIdx + 1]
|
|
writeByteInReverse digitsTable[base100digitIdx]
|
|
else:
|
|
# Alternative idea:
|
|
# We now know enough to write digits directly to the stream.
|
|
if val < 10:
|
|
write s, byte(ord('\0') + val)
|
|
else:
|
|
let base100digitIdx = val * 2
|
|
write s, digitsTable[base100digitIdx]
|
|
write s, digitsTable[base100digitIdx + 1]
|
|
|
|
write s, num.toOpenArray(pos, static(num.len - 1))
|
|
|
|
proc writeText*(s: OutputStream, x: CompiledIntTypes) =
|
|
type MatchingUInt = matchingIntType typeof(x)
|
|
|
|
if x < 0:
|
|
s.write '-'
|
|
# The `0 - x` trick below takes care of one corner case:
|
|
# How do we get the abs value of low(int)?
|
|
# The naive `-x` triggers an overflow, because low(int8)
|
|
# is -128, while high(int8) is 127.
|
|
writeText(s, MatchingUInt(0) - MatchingUInt(x))
|
|
else:
|
|
writeText(s, MatchingUInt(x))
|
|
|
|
when defined(c):
|
|
|
|
proc writeText*(s: OutputStream, x: float64|float32|float) =
|
|
## Write the floating point number to the output stream. It has less overhead
|
|
## than `$` because it is directly written to the stream without
|
|
## allocating a standard Nim 'string'.
|
|
##
|
|
## Because this procedure uses stdlib, it is only active when Nim compiles
|
|
## with the 'c' flag. Otherwise, float types are passed to the generic writeText
|
|
## procedure.
|
|
var buffer: array[65, char]
|
|
let blen = writeFloatToBuffer(buffer, x)
|
|
write s, buffer.toOpenArray(0, blen - 1)
|
|
|
|
template writeText*(s: OutputStream, str: string) =
|
|
write s, str
|
|
|
|
template writeText*(s: OutputStream, val: auto) =
|
|
write s, $val
|
|
|
|
proc writeHex*(s: OutputStream, bytes: openarray[byte]) =
|
|
const hexChars = "0123456789abcdef"
|
|
|
|
for b in bytes:
|
|
s.write hexChars[int b shr 4 and 0xF]
|
|
s.write hexChars[int b and 0xF]
|
|
|
|
proc writeHex*(s: OutputStream, chars: openarray[char]) =
|
|
writeHex s, charsToBytes(chars)
|
|
|
|
const
|
|
NewLines* = {'\r', '\n'}
|
|
Digits* = {'0'..'9'}
|
|
|
|
proc readLine*(s: InputStream, keepEol = false): TaintedString {.fsMultiSync.} =
|
|
fsAssert readableNow(s)
|
|
|
|
while s.readable:
|
|
let c = s.peek.char
|
|
if c in NewLines:
|
|
if keepEol:
|
|
result.add c
|
|
if c == '\r' and s.readable and s.peek.char == '\n':
|
|
result.add s.read.char
|
|
else:
|
|
advance s
|
|
if c == '\r' and s.readable and s.peek.char == '\n':
|
|
advance s
|
|
return
|
|
|
|
result.add s.read.char
|
|
|
|
proc readUntil*(s: InputStream,
|
|
sep: openarray[char]): Option[TaintedString] =
|
|
fsAssert readableNow(s)
|
|
var res = ""
|
|
while s.readable(sep.len):
|
|
if s.lookAheadMatch(charsToBytes(sep)):
|
|
return some(res)
|
|
res.add s.read.char
|
|
|
|
template nextLine*(sp: InputStream, keepEol = false): Option[TaintedString] =
|
|
let s = sp
|
|
if s.readable:
|
|
some readLine(s, keepEol)
|
|
else:
|
|
none string
|
|
|
|
iterator lines*(s: InputStream, keepEol = false): TaintedString =
|
|
while s.readable:
|
|
yield readLine(s, keepEol)
|
|
|
|
proc readUnsignedInt*(s: InputStream, T: type[CompiledUIntTypes]): T =
|
|
fsAssert s.readable and s.peek.char in Digits
|
|
|
|
template eatDigitAndPeek: char =
|
|
advance s
|
|
if not s.readable: return
|
|
s.peek.char
|
|
|
|
var c = s.peek.char
|
|
result = T(ord(c) - ord('0'))
|
|
c = eatDigitAndPeek()
|
|
while c.isDigit:
|
|
# TODO: How do we handle the possible overflow here?
|
|
result = result * 10 + T(ord(c) - ord('0'))
|
|
c = eatDigitAndPeek()
|
|
|
|
template readUnsignedInt*(s: InputStream): uint =
|
|
readUnsignedInt(s, uint)
|
|
|
|
proc readSignedInt*(s: InputStream, T: type[CompiledIntTypes]): Option[T] =
|
|
if s.readable:
|
|
let maybeSign = s.read.peek
|
|
if maybeSign in {'-', '+'}:
|
|
if not s.readable(2) or s.peekAt(1).char notin Digits:
|
|
return
|
|
else:
|
|
advance s
|
|
elif maybeSign notin Digits:
|
|
return
|
|
|
|
type UIntType = matchingIntType T
|
|
let uintVal = readUnsignedInt(s, UIntType)
|
|
|
|
if maybeSign == '-':
|
|
if uintVal > UIntType(max(T)) + 1:
|
|
return # Overflow. We've consumed part of the stream though.
|
|
# TODO: Should we rewind it to a previous state?
|
|
return some cast[T](UIntType(0) - uintVal)
|
|
else:
|
|
if uintVal > UIntType(max(T)):
|
|
return # Overflow. We've consumed part of the stream though.
|
|
# TODO: Should we rewind it to a previous state?
|
|
return some T(uintVal)
|
|
|