211 lines
6.3 KiB
Nim
Raw Normal View History

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)