Merge branch 'araq-strings-v1' into devel

This commit is contained in:
Araq 2018-04-30 19:25:04 +02:00
commit f74f485bf7
30 changed files with 156 additions and 1928 deletions

View file

@ -83,6 +83,9 @@
- ``nil`` for strings/seqs is finally gone. Instead the default value for - ``nil`` for strings/seqs is finally gone. Instead the default value for
these is ``"" / @[]``. these is ``"" / @[]``.
- Accessing the binary zero terminator in Nim's native strings
is now invalid. Internally a Nim string still has the trailing zero for
zero-copy interoperability with ``cstring``.
### Tool changes ### Tool changes

View file

@ -1471,7 +1471,7 @@ mysterious crashes.
**Note**: The example only works because the memory is initialized to zero **Note**: The example only works because the memory is initialized to zero
(``alloc0`` instead of ``alloc`` does this): ``d.s`` is thus initialized to (``alloc0`` instead of ``alloc`` does this): ``d.s`` is thus initialized to
``nil`` which the string assignment can handle. One needs to know low level binary zero which the string assignment can handle. One needs to know low level
details like this when mixing garbage collected data with unmanaged memory. details like this when mixing garbage collected data with unmanaged memory.
.. XXX finalizers for traced objects .. XXX finalizers for traced objects
@ -2512,8 +2512,8 @@ char '\\0'
bool false bool false
ref or pointer type nil ref or pointer type nil
procedural type nil procedural type nil
sequence nil (*not* ``@[]``) sequence ``@[]``
string nil (*not* "") string ``""``
tuple[x: A, y: B, ...] (default(A), default(B), ...) tuple[x: A, y: B, ...] (default(A), default(B), ...)
(analogous for objects) (analogous for objects)
array[0..., T] [default(T), ...] array[0..., T] [default(T), ...]
@ -4270,7 +4270,7 @@ therefore very useful for type specialization within generic code:
Table[Key, Value] = object Table[Key, Value] = object
keys: seq[Key] keys: seq[Key]
values: seq[Value] values: seq[Value]
when not (Key is string): # nil value for strings used for optimization when not (Key is string): # empty value for strings used for optimization
deletedKeys: seq[bool] deletedKeys: seq[bool]
@ -7434,8 +7434,8 @@ code generation directly, but their presence can be detected by macros.
Custom pragmas are defined using templates annotated with pragma ``pragma``: Custom pragmas are defined using templates annotated with pragma ``pragma``:
.. code-block:: nim .. code-block:: nim
template dbTable(name: string, table_space: string = nil) {.pragma.} template dbTable(name: string, table_space: string = "") {.pragma.}
template dbKey(name: string = nil, primary_key: bool = false) {.pragma.} template dbKey(name: string = "", primary_key: bool = false) {.pragma.}
template dbForeignKey(t: typedesc) {.pragma.} template dbForeignKey(t: typedesc) {.pragma.}
template dbIgnore {.pragma.} template dbIgnore {.pragma.}

View file

@ -960,11 +960,7 @@ enforced. For example, when reading strings from binary files, they are merely
a sequence of bytes. The index operation ``s[i]`` means the i-th *char* of a sequence of bytes. The index operation ``s[i]`` means the i-th *char* of
``s``, not the i-th *unichar*. ``s``, not the i-th *unichar*.
String variables are initialized with a special value, called ``nil``. However, String variables are initialized with the empty strings ``""``.
most string operations cannot deal with ``nil`` (leading to an exception being
raised) for performance reasons. It is best to use empty strings ``""``
rather than ``nil`` as the *empty* value. But ``""`` often creates a string
object on the heap, so there is a trade-off to be made here.
Integers Integers
@ -1309,11 +1305,7 @@ Example:
x: seq[int] # a reference to a sequence of integers x: seq[int] # a reference to a sequence of integers
x = @[1, 2, 3, 4, 5, 6] # the @ turns the array into a sequence allocated on the heap x = @[1, 2, 3, 4, 5, 6] # the @ turns the array into a sequence allocated on the heap
Sequence variables are initialized with ``nil``. However, most sequence Sequence variables are initialized with ``@[]``.
operations cannot deal with ``nil`` (leading to an exception being
raised) for performance reasons. Thus one should use empty sequences ``@[]``
rather than ``nil`` as the *empty* value. But ``@[]`` creates a sequence
object on the heap, so there is a trade-off to be made here.
The ``for`` statement can be used with one or two variables when used with a The ``for`` statement can be used with one or two variables when used with a
sequence. When you use the one variable form, the variable will hold the value sequence. When you use the one variable form, the variable will hold the value
@ -1355,11 +1347,9 @@ type does not matter.
.. code-block:: nim .. code-block:: nim
:test: "nim c $1" :test: "nim c $1"
var var
fruits: seq[string] # reference to a sequence of strings that is initialized with 'nil' fruits: seq[string] # reference to a sequence of strings that is initialized with '@[]'
capitals: array[3, string] # array of strings with a fixed size capitals: array[3, string] # array of strings with a fixed size
fruits = @[] # creates an empty sequence on the heap that will be referenced by 'fruits'
capitals = ["New York", "London", "Berlin"] # array 'capitals' allows assignment of only three elements capitals = ["New York", "London", "Berlin"] # array 'capitals' allows assignment of only three elements
fruits.add("Banana") # sequence 'fruits' is dynamically expandable during runtime fruits.add("Banana") # sequence 'fruits' is dynamically expandable during runtime
fruits.add("Mango") fruits.add("Mango")
@ -1691,7 +1681,7 @@ rules apply:
write(stdout, x(3)) # no error: A.x is called write(stdout, x(3)) # no error: A.x is called
write(stdout, x("")) # no error: B.x is called write(stdout, x("")) # no error: B.x is called
proc x*(a: int): string = nil proc x*(a: int): string = discard
write(stdout, x(3)) # ambiguous: which `x` is to call? write(stdout, x(3)) # ambiguous: which `x` is to call?

View file

@ -107,7 +107,7 @@ proc executeCgi(server: var TServer, client: Socket, path, query: string,
dataAvail = recvLine(client, buf) dataAvail = recvLine(client, buf)
if buf.len == 0: if buf.len == 0:
break break
var L = toLower(buf) var L = toLowerAscii(buf)
if L.startsWith("content-length:"): if L.startsWith("content-length:"):
var i = len("content-length:") var i = len("content-length:")
while L[i] in Whitespace: inc(i) while L[i] in Whitespace: inc(i)
@ -205,7 +205,7 @@ proc acceptRequest(server: var TServer, client: Socket) =
client.close() client.close()
else: else:
when defined(Windows): when defined(Windows):
var ext = splitFile(path).ext.toLower var ext = splitFile(path).ext.toLowerAscii
if ext == ".exe" or ext == ".cgi": if ext == ".exe" or ext == ".cgi":
# XXX: extract interpreter information here? # XXX: extract interpreter information here?
cgi = true cgi = true

View file

@ -10,7 +10,7 @@
from pcre import nil from pcre import nil
import nre.private.util import nre.private.util
import tables import tables
from strutils import toLower, `%` from strutils import `%`
from math import ceil from math import ceil
import options import options
from unicode import runeLenAt from unicode import runeLenAt
@ -326,15 +326,15 @@ proc `$`*(pattern: RegexMatch): string =
proc `==`*(a, b: Regex): bool = proc `==`*(a, b: Regex): bool =
if not a.isNil and not b.isNil: if not a.isNil and not b.isNil:
return a.pattern == b.pattern and return a.pattern == b.pattern and
a.pcreObj == b.pcreObj and a.pcreObj == b.pcreObj and
a.pcreExtra == b.pcreExtra a.pcreExtra == b.pcreExtra
else: else:
return system.`==`(a, b) return system.`==`(a, b)
proc `==`*(a, b: RegexMatch): bool = proc `==`*(a, b: RegexMatch): bool =
return a.pattern == b.pattern and return a.pattern == b.pattern and
a.str == b.str a.str == b.str
# }}} # }}}
# Creation & Destruction {{{ # Creation & Destruction {{{

View file

@ -201,7 +201,7 @@ when defineSsl:
flags: set[SocketFlag]) {.async.} = flags: set[SocketFlag]) {.async.} =
let len = bioCtrlPending(socket.bioOut) let len = bioCtrlPending(socket.bioOut)
if len > 0: if len > 0:
var data = newStringOfCap(len) var data = newString(len)
let read = bioRead(socket.bioOut, addr data[0], len) let read = bioRead(socket.bioOut, addr data[0], len)
assert read != 0 assert read != 0
if read < 0: if read < 0:

View file

@ -52,7 +52,7 @@ template encodeInternal(s: typed, lineLen: int, newLine: string): untyped =
if numLines > 0: inc(total, (numLines - 1) * newLine.len) if numLines > 0: inc(total, (numLines - 1) * newLine.len)
result = newString(total) result = newString(total)
var var
i = 0 i = 0
r = 0 r = 0
currLine = 0 currLine = 0
@ -76,7 +76,7 @@ template encodeInternal(s: typed, lineLen: int, newLine: string): untyped =
currLine = 0 currLine = 0
if i < s.len-1: if i < s.len-1:
let let
a = ord(s[i]) a = ord(s[i])
b = ord(s[i+1]) b = ord(s[i+1])
result[r] = cb64[a shr 2] result[r] = cb64[a shr 2]
@ -130,11 +130,11 @@ proc decode*(s: string): string =
# total is an upper bound, as we will skip arbitrary whitespace: # total is an upper bound, as we will skip arbitrary whitespace:
result = newString(total) result = newString(total)
var var
i = 0 i = 0
r = 0 r = 0
while true: while true:
while s[i] in Whitespace: inc(i) while i < s.len and s[i] in Whitespace: inc(i)
if i < s.len-3: if i < s.len-3:
let let
a = s[i].decodeByte a = s[i].decodeByte

View file

@ -74,18 +74,19 @@ proc rawInsert[T](c: var CritBitTree[T], key: string): Node[T] =
var newByte = 0 var newByte = 0
block blockX: block blockX:
while newbyte < key.len: while newbyte < key.len:
if it.key[newbyte] != key[newbyte]: let ch = if newbyte < it.key.len: it.key[newbyte] else: '\0'
newotherbits = it.key[newbyte].ord xor key[newbyte].ord if ch != key[newbyte]:
newotherbits = ch.ord xor key[newbyte].ord
break blockX break blockX
inc newbyte inc newbyte
if it.key[newbyte] != '\0': if newbyte < it.key.len:
newotherbits = it.key[newbyte].ord newotherbits = it.key[newbyte].ord
else: else:
return it return it
while (newOtherBits and (newOtherBits-1)) != 0: while (newOtherBits and (newOtherBits-1)) != 0:
newOtherBits = newOtherBits and (newOtherBits-1) newOtherBits = newOtherBits and (newOtherBits-1)
newOtherBits = newOtherBits xor 255 newOtherBits = newOtherBits xor 255
let ch = it.key[newByte] let ch = if newByte < it.key.len: it.key[newByte] else: '\0'
let dir = (1 + (ord(ch) or newOtherBits)) shr 8 let dir = (1 + (ord(ch) or newOtherBits)) shr 8
var inner: Node[T] var inner: Node[T]

View file

@ -25,16 +25,16 @@ proc parseCookies*(s: string): StringTableRef =
result = newStringTable(modeCaseInsensitive) result = newStringTable(modeCaseInsensitive)
var i = 0 var i = 0
while true: while true:
while s[i] == ' ' or s[i] == '\t': inc(i) while i < s.len and (s[i] == ' ' or s[i] == '\t'): inc(i)
var keystart = i var keystart = i
while s[i] != '=' and s[i] != '\0': inc(i) while i < s.len and s[i] != '=': inc(i)
var keyend = i-1 var keyend = i-1
if s[i] == '\0': break if i >= s.len: break
inc(i) # skip '=' inc(i) # skip '='
var valstart = i var valstart = i
while s[i] != ';' and s[i] != '\0': inc(i) while i < s.len and s[i] != ';': inc(i)
result[substr(s, keystart, keyend)] = substr(s, valstart, i-1) result[substr(s, keystart, keyend)] = substr(s, valstart, i-1)
if s[i] == '\0': break if i >= s.len: break
inc(i) # skip ';' inc(i) # skip ';'
proc setCookie*(key, value: string, domain = "", path = "", proc setCookie*(key, value: string, domain = "", path = "",

View file

@ -36,7 +36,7 @@ when defined(windows):
while i < a.len and j < b.len: while i < a.len and j < b.len:
if a[i] in {'-', '_'}: inc i if a[i] in {'-', '_'}: inc i
if b[j] in {'-', '_'}: inc j if b[j] in {'-', '_'}: inc j
if a[i].toLower != b[j].toLower: return false if i < a.len and j < b.len and a[i].toLowerAscii != b[j].toLowerAscii: return false
inc i inc i
inc j inc j
result = i == a.len and j == b.len result = i == a.len and j == b.len

View file

@ -74,7 +74,7 @@
## ##
## .. code-block:: Nim ## .. code-block:: Nim
## import asyncdispatch, httpclient ## import asyncdispatch, httpclient
## ##
## proc onProgressChanged(total, progress, speed: BiggestInt) {.async.} = ## proc onProgressChanged(total, progress, speed: BiggestInt) {.async.} =
## echo("Downloaded ", progress, " of ", total) ## echo("Downloaded ", progress, " of ", total)
## echo("Current rate: ", speed div 1000, "kb/s") ## echo("Current rate: ", speed div 1000, "kb/s")
@ -247,7 +247,7 @@ proc parseChunks(s: Socket, timeout: int): string =
var i = 0 var i = 0
if chunkSizeStr == "": if chunkSizeStr == "":
httpError("Server terminated connection prematurely") httpError("Server terminated connection prematurely")
while true: while i < chunkSizeStr.len:
case chunkSizeStr[i] case chunkSizeStr[i]
of '0'..'9': of '0'..'9':
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0')) chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0'))
@ -255,8 +255,6 @@ proc parseChunks(s: Socket, timeout: int): string =
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10) chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10)
of 'A'..'F': of 'A'..'F':
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10) chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10)
of '\0':
break
of ';': of ';':
# http://tools.ietf.org/html/rfc2616#section-3.6.1 # http://tools.ietf.org/html/rfc2616#section-3.6.1
# We don't care about chunk-extensions. # We don't care about chunk-extensions.
@ -938,7 +936,7 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[void]
var i = 0 var i = 0
if chunkSizeStr == "": if chunkSizeStr == "":
httpError("Server terminated connection prematurely") httpError("Server terminated connection prematurely")
while true: while i < chunkSizeStr.len:
case chunkSizeStr[i] case chunkSizeStr[i]
of '0'..'9': of '0'..'9':
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0')) chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0'))
@ -946,8 +944,6 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[void]
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10) chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10)
of 'A'..'F': of 'A'..'F':
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10) chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10)
of '\0':
break
of ';': of ';':
# http://tools.ietf.org/html/rfc2616#section-3.6.1 # http://tools.ietf.org/html/rfc2616#section-3.6.1
# We don't care about chunk-extensions. # We don't care about chunk-extensions.

View file

@ -190,11 +190,11 @@ proc len*(headers: HttpHeaders): int = return headers.table.len
proc parseList(line: string, list: var seq[string], start: int): int = proc parseList(line: string, list: var seq[string], start: int): int =
var i = 0 var i = 0
var current = "" var current = ""
while line[start + i] notin {'\c', '\l', '\0'}: while start+i < line.len and line[start + i] notin {'\c', '\l'}:
i += line.skipWhitespace(start + i) i += line.skipWhitespace(start + i)
i += line.parseUntil(current, {'\c', '\l', ','}, start + i) i += line.parseUntil(current, {'\c', '\l', ','}, start + i)
list.add(current) list.add(current)
if line[start + i] == ',': if start+i < line.len and line[start + i] == ',':
i.inc # Skip , i.inc # Skip ,
current.setLen(0) current.setLen(0)

View file

@ -126,7 +126,7 @@ when false:
var dataAvail = false var dataAvail = false
while dataAvail: while dataAvail:
dataAvail = recvLine(client, buf) # TODO: This is incorrect. dataAvail = recvLine(client, buf) # TODO: This is incorrect.
var L = toLower(buf.string) var L = toLowerAscii(buf.string)
if L.startsWith("content-length:"): if L.startsWith("content-length:"):
var i = len("content-length:") var i = len("content-length:")
while L[i] in Whitespace: inc(i) while L[i] in Whitespace: inc(i)
@ -199,7 +199,7 @@ when false:
notFound(client) notFound(client)
else: else:
when defined(Windows): when defined(Windows):
var ext = splitFile(path).ext.toLower var ext = splitFile(path).ext.toLowerAscii
if ext == ".exe" or ext == ".cgi": if ext == ".exe" or ext == ".cgi":
# XXX: extract interpreter information here? # XXX: extract interpreter information here?
cgi = true cgi = true
@ -303,7 +303,7 @@ proc next*(s: var Server) =
if s.reqMethod == "POST": if s.reqMethod == "POST":
# Check for Expect header # Check for Expect header
if s.headers.hasKey("Expect"): if s.headers.hasKey("Expect"):
if s.headers["Expect"].toLower == "100-continue": if s.headers["Expect"].toLowerAscii == "100-continue":
s.client.sendStatus("100 Continue") s.client.sendStatus("100 Continue")
else: else:
s.client.sendStatus("417 Expectation Failed") s.client.sendStatus("417 Expectation Failed")
@ -427,7 +427,7 @@ proc nextAsync(s: PAsyncHTTPServer) =
if s.reqMethod == "POST": if s.reqMethod == "POST":
# Check for Expect header # Check for Expect header
if s.headers.hasKey("Expect"): if s.headers.hasKey("Expect"):
if s.headers["Expect"].toLower == "100-continue": if s.headers["Expect"].toLowerAscii == "100-continue":
s.client.sendStatus("100 Continue") s.client.sendStatus("100 Continue")
else: else:
s.client.sendStatus("417 Expectation Failed") s.client.sendStatus("417 Expectation Failed")

View file

@ -126,7 +126,7 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str
var v = "" var v = ""
let app = when defined(js): "" else: getAppFilename() let app = when defined(js): "" else: getAppFilename()
while frmt[i] in IdentChars: while frmt[i] in IdentChars:
v.add(toLower(frmt[i])) v.add(toLowerAscii(frmt[i]))
inc(i) inc(i)
case v case v
of "date": result.add(getDateStr()) of "date": result.add(getDateStr())

View file

@ -29,21 +29,21 @@ proc validEmailAddress*(s: string): bool {.noSideEffect,
chars = Letters + Digits + {'!','#','$','%','&', chars = Letters + Digits + {'!','#','$','%','&',
'\'','*','+','/','=','?','^','_','`','{','}','|','~','-','.'} '\'','*','+','/','=','?','^','_','`','{','}','|','~','-','.'}
var i = 0 var i = 0
if s[i] notin chars or s[i] == '.': return false if i >= s.len or s[i] notin chars or s[i] == '.': return false
while s[i] in chars: while i < s.len and s[i] in chars:
if s[i] == '.' and s[i+1] == '.': return false if i+1 < s.len and s[i] == '.' and s[i+1] == '.': return false
inc(i) inc(i)
if s[i] != '@': return false if i >= s.len or s[i] != '@': return false
var j = len(s)-1 var j = len(s)-1
if s[j] notin Letters: return false if j >= 0 and s[j] notin Letters: return false
while j >= i and s[j] in Letters: dec(j) while j >= i and s[j] in Letters: dec(j)
inc(i) # skip '@' inc(i) # skip '@'
while s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i) while i < s.len and s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i)
if s[i] != '\0': return false if i != s.len: return false
var x = substr(s, j+1) var x = substr(s, j+1)
if len(x) == 2 and x[0] in Letters and x[1] in Letters: return true if len(x) == 2 and x[0] in Letters and x[1] in Letters: return true
case toLower(x) case toLowerAscii(x)
of "com", "org", "net", "gov", "mil", "biz", "info", "mobi", "name", of "com", "org", "net", "gov", "mil", "biz", "info", "mobi", "name",
"aero", "jobs", "museum": return true "aero", "jobs", "museum": return true
else: return false else: return false

View file

@ -1060,18 +1060,17 @@ proc parseCmdLine*(c: string): seq[string] {.
while true: while true:
setLen(a, 0) setLen(a, 0)
# eat all delimiting whitespace # eat all delimiting whitespace
while c[i] == ' ' or c[i] == '\t' or c[i] == '\l' or c[i] == '\r' : inc(i) while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i)
if i >= c.len: break
when defined(windows): when defined(windows):
# parse a single argument according to the above rules: # parse a single argument according to the above rules:
if c[i] == '\0': break
var inQuote = false var inQuote = false
while true: while i < c.len:
case c[i] case c[i]
of '\0': break
of '\\': of '\\':
var j = i var j = i
while c[j] == '\\': inc(j) while j < c.len and c[j] == '\\': inc(j)
if c[j] == '"': if j < c.len and c[j] == '"':
for k in 1..(j-i) div 2: a.add('\\') for k in 1..(j-i) div 2: a.add('\\')
if (j-i) mod 2 == 0: if (j-i) mod 2 == 0:
i = j i = j
@ -1084,7 +1083,7 @@ proc parseCmdLine*(c: string): seq[string] {.
of '"': of '"':
inc(i) inc(i)
if not inQuote: inQuote = true if not inQuote: inQuote = true
elif c[i] == '"': elif i < c.len and c[i] == '"':
a.add(c[i]) a.add(c[i])
inc(i) inc(i)
else: else:
@ -1102,13 +1101,12 @@ proc parseCmdLine*(c: string): seq[string] {.
of '\'', '\"': of '\'', '\"':
var delim = c[i] var delim = c[i]
inc(i) # skip ' or " inc(i) # skip ' or "
while c[i] != '\0' and c[i] != delim: while i < c.len and c[i] != delim:
add a, c[i] add a, c[i]
inc(i) inc(i)
if c[i] != '\0': inc(i) if i < c.len: inc(i)
of '\0': break
else: else:
while c[i] > ' ': while i < c.len and c[i] > ' ':
add(a, c[i]) add(a, c[i])
inc(i) inc(i)
add(result, a) add(result, a)

View file

@ -196,7 +196,7 @@ proc joinPath*(head, tail: string): string {.
else: else:
result = head & tail result = head & tail
else: else:
if tail[0] in {DirSep, AltSep}: if tail.len > 0 and tail[0] in {DirSep, AltSep}:
result = head & tail result = head & tail
else: else:
result = head & DirSep & tail result = head & DirSep & tail
@ -477,7 +477,7 @@ proc unixToNativePath*(path: string, drive=""): string {.
var i = start var i = start
while i < len(path): # ../../../ --> :::: while i < len(path): # ../../../ --> ::::
if path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/':
# parent directory # parent directory
when defined(macos): when defined(macos):
if result[high(result)] == ':': if result[high(result)] == ':':

View file

@ -534,15 +534,15 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
case p.kind case p.kind
of pkEmpty: result = 0 # match of length 0 of pkEmpty: result = 0 # match of length 0
of pkAny: of pkAny:
if s[start] != '\0': result = 1 if start < s.len: result = 1
else: result = -1 else: result = -1
of pkAnyRune: of pkAnyRune:
if s[start] != '\0': if start < s.len:
result = runeLenAt(s, start) result = runeLenAt(s, start)
else: else:
result = -1 result = -1
of pkLetter: of pkLetter:
if s[start] != '\0': if start < s.len:
var a: Rune var a: Rune
result = start result = start
fastRuneAt(s, result, a) fastRuneAt(s, result, a)
@ -551,7 +551,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
else: else:
result = -1 result = -1
of pkLower: of pkLower:
if s[start] != '\0': if start < s.len:
var a: Rune var a: Rune
result = start result = start
fastRuneAt(s, result, a) fastRuneAt(s, result, a)
@ -560,7 +560,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
else: else:
result = -1 result = -1
of pkUpper: of pkUpper:
if s[start] != '\0': if start < s.len:
var a: Rune var a: Rune
result = start result = start
fastRuneAt(s, result, a) fastRuneAt(s, result, a)
@ -569,7 +569,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
else: else:
result = -1 result = -1
of pkTitle: of pkTitle:
if s[start] != '\0': if start < s.len:
var a: Rune var a: Rune
result = start result = start
fastRuneAt(s, result, a) fastRuneAt(s, result, a)
@ -578,7 +578,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
else: else:
result = -1 result = -1
of pkWhitespace: of pkWhitespace:
if s[start] != '\0': if start < s.len:
var a: Rune var a: Rune
result = start result = start
fastRuneAt(s, result, a) fastRuneAt(s, result, a)
@ -589,15 +589,15 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
of pkGreedyAny: of pkGreedyAny:
result = len(s) - start result = len(s) - start
of pkNewLine: of pkNewLine:
if s[start] == '\L': result = 1 if start < s.len and s[start] == '\L': result = 1
elif s[start] == '\C': elif start < s.len and s[start] == '\C':
if s[start+1] == '\L': result = 2 if start+1 < s.len and s[start+1] == '\L': result = 2
else: result = 1 else: result = 1
else: result = -1 else: result = -1
of pkTerminal: of pkTerminal:
result = len(p.term) result = len(p.term)
for i in 0..result-1: for i in 0..result-1:
if p.term[i] != s[start+i]: if start+i >= s.len or p.term[i] != s[start+i]:
result = -1 result = -1
break break
of pkTerminalIgnoreCase: of pkTerminalIgnoreCase:
@ -606,6 +606,9 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
a, b: Rune a, b: Rune
result = start result = start
while i < len(p.term): while i < len(p.term):
if result >= s.len:
result = -1
break
fastRuneAt(p.term, i, a) fastRuneAt(p.term, i, a)
fastRuneAt(s, result, b) fastRuneAt(s, result, b)
if toLower(a) != toLower(b): if toLower(a) != toLower(b):
@ -621,18 +624,23 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
while true: while true:
fastRuneAt(p.term, i, a) fastRuneAt(p.term, i, a)
if a != Rune('_'): break if a != Rune('_'): break
while true: while result < s.len:
fastRuneAt(s, result, b) fastRuneAt(s, result, b)
if b != Rune('_'): break if b != Rune('_'): break
if toLower(a) != toLower(b): if result >= s.len:
if i >= p.term.len: break
else:
result = -1
break
elif toLower(a) != toLower(b):
result = -1 result = -1
break break
dec(result, start) dec(result, start)
of pkChar: of pkChar:
if p.ch == s[start]: result = 1 if start < s.len and p.ch == s[start]: result = 1
else: result = -1 else: result = -1
of pkCharChoice: of pkCharChoice:
if contains(p.charChoice[], s[start]): result = 1 if start < s.len and contains(p.charChoice[], s[start]): result = 1
else: result = -1 else: result = -1
of pkNonTerminal: of pkNonTerminal:
var oldMl = c.ml var oldMl = c.ml
@ -695,10 +703,10 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
of pkGreedyRepChar: of pkGreedyRepChar:
result = 0 result = 0
var ch = p.ch var ch = p.ch
while ch == s[start+result]: inc(result) while start+result < s.len and ch == s[start+result]: inc(result)
of pkGreedyRepSet: of pkGreedyRepSet:
result = 0 result = 0
while contains(p.charChoice[], s[start+result]): inc(result) while start+result < s.len and contains(p.charChoice[], s[start+result]): inc(result)
of pkOption: of pkOption:
result = max(0, rawMatch(s, p.sons[0], start, c)) result = max(0, rawMatch(s, p.sons[0], start, c))
of pkAndPredicate: of pkAndPredicate:

View file

@ -1126,7 +1126,7 @@ proc unindent*(s: string, count: Natural, padding: string = " "): string
var indentCount = 0 var indentCount = 0
for j in 0..<count.int: for j in 0..<count.int:
indentCount.inc indentCount.inc
if line[j .. j + padding.len-1] != padding: if j + padding.len-1 >= line.len or line[j .. j + padding.len-1] != padding:
indentCount = j indentCount = j
break break
result.add(line[indentCount*padding.len .. ^1]) result.add(line[indentCount*padding.len .. ^1])

View file

@ -1318,24 +1318,23 @@ proc format*(dt: DateTime, f: string): string {.tags: [].}=
result = "" result = ""
var i = 0 var i = 0
var currentF = "" var currentF = ""
while true: while i < f.len:
case f[i] case f[i]
of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',':
formatToken(dt, currentF, result) formatToken(dt, currentF, result)
currentF = "" currentF = ""
if f[i] == '\0': break
if f[i] == '\'': if f[i] == '\'':
inc(i) # Skip ' inc(i) # Skip '
while f[i] != '\'' and f.len-1 > i: while i < f.len-1 and f[i] != '\'':
result.add(f[i]) result.add(f[i])
inc(i) inc(i)
else: result.add(f[i]) else: result.add(f[i])
else: else:
# Check if the letter being added matches previous accumulated buffer. # Check if the letter being added matches previous accumulated buffer.
if currentF.len < 1 or currentF[high(currentF)] == f[i]: if currentF.len == 0 or currentF[high(currentF)] == f[i]:
currentF.add(f[i]) currentF.add(f[i])
else: else:
formatToken(dt, currentF, result) formatToken(dt, currentF, result)
@ -1343,6 +1342,7 @@ proc format*(dt: DateTime, f: string): string {.tags: [].}=
currentF = "" currentF = ""
inc(i) inc(i)
formatToken(dt, currentF, result)
proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} =
## Converts a `DateTime` object to a string representation. ## Converts a `DateTime` object to a string representation.
@ -1439,58 +1439,58 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) =
dt.month = month.Month dt.month = month.Month
of "MMM": of "MMM":
case value[j..j+2].toLowerAscii(): case value[j..j+2].toLowerAscii():
of "jan": dt.month = mJan of "jan": dt.month = mJan
of "feb": dt.month = mFeb of "feb": dt.month = mFeb
of "mar": dt.month = mMar of "mar": dt.month = mMar
of "apr": dt.month = mApr of "apr": dt.month = mApr
of "may": dt.month = mMay of "may": dt.month = mMay
of "jun": dt.month = mJun of "jun": dt.month = mJun
of "jul": dt.month = mJul of "jul": dt.month = mJul
of "aug": dt.month = mAug of "aug": dt.month = mAug
of "sep": dt.month = mSep of "sep": dt.month = mSep
of "oct": dt.month = mOct of "oct": dt.month = mOct
of "nov": dt.month = mNov of "nov": dt.month = mNov
of "dec": dt.month = mDec of "dec": dt.month = mDec
else: else:
raise newException(ValueError, raise newException(ValueError,
"Couldn't parse month (MMM), got: " & value) "Couldn't parse month (MMM), got: " & value)
j += 3 j += 3
of "MMMM": of "MMMM":
if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0: if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0:
dt.month = mJan dt.month = mJan
j += 7 j += 7
elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0: elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0:
dt.month = mFeb dt.month = mFeb
j += 8 j += 8
elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0: elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0:
dt.month = mMar dt.month = mMar
j += 5 j += 5
elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0: elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0:
dt.month = mApr dt.month = mApr
j += 5 j += 5
elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0: elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0:
dt.month = mMay dt.month = mMay
j += 3 j += 3
elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0: elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0:
dt.month = mJun dt.month = mJun
j += 4 j += 4
elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0: elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0:
dt.month = mJul dt.month = mJul
j += 4 j += 4
elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0: elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0:
dt.month = mAug dt.month = mAug
j += 6 j += 6
elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0: elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0:
dt.month = mSep dt.month = mSep
j += 9 j += 9
elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0: elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0:
dt.month = mOct dt.month = mOct
j += 7 j += 7
elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0: elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0:
dt.month = mNov dt.month = mNov
j += 8 j += 8
elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0: elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0:
dt.month = mDec dt.month = mDec
j += 8 j += 8
else: else:
raise newException(ValueError, raise newException(ValueError,
@ -1521,44 +1521,47 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) =
j += 4 j += 4
of "z": of "z":
dt.isDst = false dt.isDst = false
if value[j] == '+': let ch = if j < value.len: value[j] else: '\0'
if ch == '+':
dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour
elif value[j] == '-': elif ch == '-':
dt.utcOffset = parseInt($value[j+1]) * secondsInHour dt.utcOffset = parseInt($value[j+1]) * secondsInHour
elif value[j] == 'Z': elif ch == 'Z':
dt.utcOffset = 0 dt.utcOffset = 0
j += 1 j += 1
return return
else: else:
raise newException(ValueError, raise newException(ValueError,
"Couldn't parse timezone offset (z), got: " & value[j]) "Couldn't parse timezone offset (z), got: " & ch)
j += 2 j += 2
of "zz": of "zz":
dt.isDst = false dt.isDst = false
if value[j] == '+': let ch = if j < value.len: value[j] else: '\0'
if ch == '+':
dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour
elif value[j] == '-': elif ch == '-':
dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour
elif value[j] == 'Z': elif ch == 'Z':
dt.utcOffset = 0 dt.utcOffset = 0
j += 1 j += 1
return return
else: else:
raise newException(ValueError, raise newException(ValueError,
"Couldn't parse timezone offset (zz), got: " & value[j]) "Couldn't parse timezone offset (zz), got: " & ch)
j += 3 j += 3
of "zzz": of "zzz":
dt.isDst = false dt.isDst = false
var factor = 0 var factor = 0
if value[j] == '+': factor = -1 let ch = if j < value.len: value[j] else: '\0'
elif value[j] == '-': factor = 1 if ch == '+': factor = -1
elif value[j] == 'Z': elif ch == '-': factor = 1
elif ch == 'Z':
dt.utcOffset = 0 dt.utcOffset = 0
j += 1 j += 1
return return
else: else:
raise newException(ValueError, raise newException(ValueError,
"Couldn't parse timezone offset (zzz), got: " & value[j]) "Couldn't parse timezone offset (zzz), got: " & ch)
dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour
j += 4 j += 4
dt.utcOffset += factor * value[j..j+1].parseInt() * 60 dt.utcOffset += factor * value[j..j+1].parseInt() * 60
@ -1620,20 +1623,18 @@ proc parse*(value, layout: string, zone: Timezone = local()): DateTime =
dt.nanosecond = 0 dt.nanosecond = 0
dt.isDst = true # using this is flag for checking whether a timezone has \ dt.isDst = true # using this is flag for checking whether a timezone has \
# been read (because DST is always false when a tz is parsed) # been read (because DST is always false when a tz is parsed)
while true: while i < layout.len:
case layout[i] case layout[i]
of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',':
if token.len > 0: if token.len > 0:
parseToken(dt, token, value, j) parseToken(dt, token, value, j)
# Reset token # Reset token
token = "" token = ""
# Break if at end of line
if layout[i] == '\0': break
# Skip separator and everything between single quotes # Skip separator and everything between single quotes
# These are literals in both the layout and the value string # These are literals in both the layout and the value string
if layout[i] == '\'': if layout[i] == '\'':
inc(i) inc(i)
while layout[i] != '\'' and layout.len-1 > i: while i < layout.len-1 and layout[i] != '\'':
inc(i) inc(i)
inc(j) inc(j)
inc(i) inc(i)
@ -1642,13 +1643,15 @@ proc parse*(value, layout: string, zone: Timezone = local()): DateTime =
inc(j) inc(j)
else: else:
# Check if the letter being added matches previous accumulated buffer. # Check if the letter being added matches previous accumulated buffer.
if token.len < 1 or token[high(token)] == layout[i]: if token.len == 0 or token[high(token)] == layout[i]:
token.add(layout[i]) token.add(layout[i])
inc(i) inc(i)
else: else:
parseToken(dt, token, value, j) parseToken(dt, token, value, j)
token = "" token = ""
if i >= layout.len and token.len > 0:
parseToken(dt, token, value, j)
if dt.isDst: if dt.isDst:
# No timezone parsed - assume timezone is `zone` # No timezone parsed - assume timezone is `zone`
result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone)

View file

@ -232,10 +232,10 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
# Exceptions # Exceptions
if qualifiedName.contains(':'): if qualifiedName.contains(':'):
let qfnamespaces = qualifiedName.toLower().split(':') let qfnamespaces = qualifiedName.toLowerAscii().split(':')
if isNil(namespaceURI): if isNil(namespaceURI):
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil")
elif qfnamespaces[0] == "xml" and elif qfnamespaces[0] == "xml" and
namespaceURI != "http://www.w3.org/XML/1998/namespace" and namespaceURI != "http://www.w3.org/XML/1998/namespace" and
qfnamespaces[1] notin stdattrnames: qfnamespaces[1] notin stdattrnames:
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
@ -311,10 +311,10 @@ proc createElement*(doc: PDocument, tagName: string): PElement =
proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement = proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement =
## Creates an element of the given qualified name and namespace URI. ## Creates an element of the given qualified name and namespace URI.
if qualifiedName.contains(':'): if qualifiedName.contains(':'):
let qfnamespaces = qualifiedName.toLower().split(':') let qfnamespaces = qualifiedName.toLowerAscii().split(':')
if isNil(namespaceURI): if isNil(namespaceURI):
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil")
elif qfnamespaces[0] == "xml" and elif qfnamespaces[0] == "xml" and
namespaceURI != "http://www.w3.org/XML/1998/namespace" and namespaceURI != "http://www.w3.org/XML/1998/namespace" and
qfnamespaces[1] notin stdattrnames: qfnamespaces[1] notin stdattrnames:
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
@ -533,13 +533,13 @@ proc `prefix=`*(n: PNode, value: string) =
if isNil(n.fNamespaceURI): if isNil(n.fNamespaceURI):
raise newException(ENamespaceErr, "namespaceURI cannot be nil") raise newException(ENamespaceErr, "namespaceURI cannot be nil")
elif value.toLower() == "xml" and n.fNamespaceURI != "http://www.w3.org/XML/1998/namespace": elif value.toLowerAscii() == "xml" and n.fNamespaceURI != "http://www.w3.org/XML/1998/namespace":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
"When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") "When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"")
elif value.toLower() == "xmlns" and n.fNamespaceURI != "http://www.w3.org/2000/xmlns/": elif value.toLowerAscii() == "xmlns" and n.fNamespaceURI != "http://www.w3.org/2000/xmlns/":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
"When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"") "When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"")
elif value.toLower() == "xmlns" and n.fNodeType == AttributeNode: elif value.toLowerAscii() == "xmlns" and n.fNodeType == AttributeNode:
raise newException(ENamespaceErr, "An AttributeNode cannot have a prefix of \"xmlns\"") raise newException(ENamespaceErr, "An AttributeNode cannot have a prefix of \"xmlns\"")
n.fNodeName = value & ":" & n.fLocalName n.fNodeName = value & ":" & n.fLocalName

View file

@ -1,7 +1,7 @@
# bug 2007 # bug 2007
import asyncdispatch, asyncnet, logging, json, uri, strutils, future import asyncdispatch, asyncnet, logging, json, uri, strutils, sugar
type type
Builder = ref object Builder = ref object
@ -27,7 +27,7 @@ proc newBuild*(onProgress: ProgressCB): Build =
result.onProgress = onProgress result.onProgress = onProgress
proc start(build: Build, repo, hash: string) {.async.} = proc start(build: Build, repo, hash: string) {.async.} =
let path = repo.parseUri().path.toLower() let path = repo.parseUri().path.toLowerAscii()
proc onProgress(builder: Builder, message: string) {.async.} = proc onProgress(builder: Builder, message: string) {.async.} =
debug($message) debug($message)

View file

@ -31,7 +31,7 @@ type
s.pop() is T s.pop() is T
type ValueType = T type ValueType = T
const ValueTypeName = T.name.toUpper const ValueTypeName = T.name.toUpperAscii
proc genericAlgorithm[T](s: var Stack[T], y: T) = proc genericAlgorithm[T](s: var Stack[T], y: T) =
static: static:

View file

@ -7,7 +7,7 @@ task "install", "compile and install nake binary":
for index, dir in pairs(path): for index, dir in pairs(path):
echo " ", index, ". ", dir echo " ", index, ". ", dir
echo "Where to install nake binary? (quit with ^C or quit or exit)" echo "Where to install nake binary? (quit with ^C or quit or exit)"
let ans = stdin.readLine().toLower let ans = stdin.readLine().toLowerAscii
var index = 0 var index = 0
case ans case ans
of "q", "quit", "x", "exit": of "q", "quit", "x", "exit":

View file

@ -562,7 +562,7 @@ proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord =
result.useSound = importSound(data[2], errors, "useSound") result.useSound = importSound(data[2], errors, "useSound")
case data[1].str.toLower case data[1].str.toLowerAscii
of "projectile": of "projectile":
result.kind = Projectile result.kind = Projectile
if data[2]["bullet"].kind == JString: if data[2]["bullet"].kind == JString:

View file

@ -88,7 +88,7 @@ task "download", "download game assets":
if existsFile(path): if existsFile(path):
echo "The file already exists\n", echo "The file already exists\n",
"[R]emove [M]ove [Q]uit [S]kip Source: ", GameAssets "[R]emove [M]ove [Q]uit [S]kip Source: ", GameAssets
case stdin.readLine.toLower case stdin.readLine.toLowerAscii
of "r": of "r":
removeFile path removeFile path
of "m": of "m":
@ -120,7 +120,7 @@ task "download", "download game assets":
echo "Download binary libs? Only libs for linux are available currently, enjoy the irony.\n", echo "Download binary libs? Only libs for linux are available currently, enjoy the irony.\n",
"[Y]es [N]o Source: ", BinLibs "[Y]es [N]o Source: ", BinLibs
case stdin.readline.toLower case stdin.readline.toLowerAscii
of "y", "yes": of "y", "yes":
discard ## o_O discard ## o_O
else: else:

View file

@ -6,7 +6,7 @@ discard """
import strutils import strutils
var x = "hello world!".toLower.toUpper var x = "hello world!".toLowerAscii.toUpperAscii
x.echo() x.echo()
#OUT HELLO WORLD! #OUT HELLO WORLD!

View file

@ -8,7 +8,7 @@ import strutils
proc foo(s: static[string]): string = proc foo(s: static[string]): string =
static: echo s static: echo s
const R = s.toUpper const R = s.toUpperAscii
return R return R
echo foo("test 1") echo foo("test 1")

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,6 @@ else:
result = newString(size) result = newString(size)
let res = WideCharToMultiByte(CP_UTF8, 0'i32, cast[LPWCSTR](addr(wc[0])), wclen, let res = WideCharToMultiByte(CP_UTF8, 0'i32, cast[LPWCSTR](addr(wc[0])), wclen,
cstring(result), size, cstring(nil), LPBOOL(nil)) cstring(result), size, cstring(nil), LPBOOL(nil))
result[size] = chr(0)
doAssert size == res doAssert size == res
proc testCP(wc: WideCString, lo, hi: int) = proc testCP(wc: WideCString, lo, hi: int) =