Mercurial > games > semicongine
view semiconginev2/thirdparty/winim/winim/winstr.nim @ 1250:9ceb509af5ea
add: loading of most kinds of data from gltf
author | sam <sam@basx.dev> |
---|---|
date | Thu, 25 Jul 2024 23:15:05 +0700 |
parents | 56781cc0fc7c |
children |
line wrap: on
line source
#==================================================================== # # Winim - Windows API, COM, and CLR Module for Nim # Copyright (c) Chen Kai-Hung, Ward # # Windows String Type Utilities # #==================================================================== ## This module contains new string types and utilities to deal with strings in Windows. ## Windows SDK use following types to represent a char or a string: ## ## .. code-block:: Nim ## type ## CHAR = char ## WCHAR = uint16 ## LPSTR|LPCSTR = ptr CHAR # however, it should be ansi string, not utf8 string ## LPWSTR|LPCWSTR = ptr WCHAR ## BSTR = distinct ptr WCHAR # BSTR is not binary compatible with LPWSTR ## (ptr) array[I, CHAR] # sometimes string defined as array[1, CHAR] but not only one char ## (ptr) array[I, WCHAR] # sometimes string defined as array[1, WCHAR] but not only one widechar ## ## By default, Nim's string type is utf8 encoding. ## However, Windows use wide character string (aka. unicode string) or multibyte character string ## (aka. ansi string). So, this module introduce following string types. ## ## .. code-block:: Nim ## type ## string # nim built-in string type, utf8 encoding by default, can be ansi string sometimes. ## cstring # compatible to the type char* in Ansi C ## wstring = distinct string # new string type to store unicode string ## mstring = distinct string # new string type to store ansi string ## ## Some type classes are also defined for convenience to deal with strings. ## ## .. code-block:: Nim ## type ## SomeChar = byte | char | WCHAR ## SomeString = string | mstring | wstring ## SomeBuffer[I] = ptr SomeChar | array[I, SomeChar] | ptr array[I, SomeChar] | ## ptr UncheckedArray[SomeChar] | openArray[SomeChar] | seq[SomeChar] ## Stringable = SomeChar | SomeString | SomeBuffer | cstring | BSTR ## ## Here are the pseudocode for most useful functions introduced by this module. ## ## .. code-block:: Nim ## proc `&`(s: cstring|string|wstring|mstring): pointer ## # Get address of the first char of a string. ## # For string, it has a similar meaning to cstring(s). ## ## proc `$`(x: Stringable): string ## proc `+$`(x: Stringable): wstring ## proc `-$`(x: Stringable): mstring ## # Convert any stringable type to string, wstring, or mstring. ## # These operators assume string|cstring|ptr char|openArray[char] are utf8 encoding. ## # setOpenArrayStringable() can be used to switch the behavior of `$` operator. ## ## proc `$$`(x: Stringable): string ## proc `+$$`(x: Stringable): wstring ## proc `-$$`(x: Stringable): mstring ## # Convert any stringable type to string, wstring, or mstring. ## # These operators assume string|cstring|ptr char|openArray[char] are ansi encoding. ## # For mstring|wstring|LPWSTR etc, these operators are the same as `$`, `+$`, `-$`. ## ## template `<<`(s: SomeString, b: SomeBuffer) ## template `<<`(b: SomeBuffer, s: SomeString) ## template `<<<`(b: SomeBuffer, s: SomeString) ## template `>>`(a: typed, b: typed) = b << a ## template `>>>`(a: typed, b: typed) = b <<< a ## # String << Buffer or Buffer >> String: Fill string by buffer. ## # Buffer << String or String >> Buffer: Fill buffer by string. ## # Buffer <<< String or String >>> Buffer: Fill buffer by string, include a null. ## ## # These operators don't convert the encoding (copy byte by byte). ## # Please make sure both side have the same character size. ## # If destination don't have the length information (e.g. pointer or UncheckedArray), ## # please make sure the buffer size is large enough. ## ## proc nullTerminate(s: var SomeString) ## # Assume a string is null terminated and set the correct length. ## ## proc nullTerminated[T: SomeString](s: T): T ## # Assume a string is null terminated and return the length-corrected string. ## ## template L(s: string): wstring ## # Generate wstring at compile-time if possible. ## # Only const string or string literal can be converted to unicode string at compile-time, ## # otherwise it is just `+$`. ## ## template T(s: string): mstring|wstring ## # Generate wstring or mstring depend on conditional symbol: useWinAnsi. ## # For example: (this code works under both unicode and ansi mode) ## ## MessageBox(0, T"hello, world", T"Nim is Powerful 中文測試", 0) ## ## template T(n: Natural): mstring|wstring ## # Generate wstring or mstring buffer depend on conditional symbol: useWinAnsi. ## # Use `&` to get the buffer address and then pass to Windows API. ## ## converter winstrConverter(s: SomeString): SomeBuffer ## # With these converters, passing string to Windows API is more easy. ## # Following converters don't need encoding conversion: ## # string => LPSTR|ptr char ## # mstring => LPSTR|ptr char ## # wstring => LPWSTR|BSTR ## # cstring => ptr char ## # BSTR => LPWSTR ## # ## # Some converters DO need encoding conversion (utf8 to unicode). ## # New memory block will be allocated. However, they are useful and convenience. ## # cstring|string => LPWSTR|BSTR ## ## There are also new string functions to deal with wstring and mstring just like built-in string type. ## ## .. code-block:: Nim ## proc newWString(len: Natural): wstring ## # Generate wstring buffer ## proc newMString(len: Natural): mstring ## # Generate mstring buffer ## ## proc setLen(s: var mstring|wstring, newLen: Natural) ## proc substr(s: wstring|mstring, first = 0): wstring|mstring ## proc substr(s: wstring|mstring, first, last: int): wstring|mstring ## proc len(s: wstring|mstring): int ## proc high(s: wstring|mstring): int ## proc low(s: wstring|mstring): int ## proc repr(s: wstring|mstring): string ## proc toHex(s: wstring|mstring): string ## ## proc `[]`(s: wstring|mstring, i: int): WCHAR|mstring ## proc `[]=`(s: wstring|mstring, i: int, u: WCHAR|CHAR) ## proc `[]=`(s: wstring|mstring, i: int, u: wstring|mstring) ## proc `[]`(s: wstring|mstring, x: HSlice) ## proc `[]=`(s: var wstring|var mstring, x: HSlice[int], b: wstring|mstring) ## proc `==`(x, y: wstring|mstring): bool ## proc `<=`(x, y: wstring|mstring): bool ## proc `<`(x, y: wstring|mstring): bool ## proc cmp(x, y: wstring|mstring): int ## proc `&`(s: wstring|mstring, t: wstring|mstring): wstring|mstring ## ## iterator items(s: wstring|mstring): WCHAR|mstring ## iterator mitems(s: var wstring): WCHAR ## iterator pairs(s: wstring|mstring): tuple[key: int|mIndex, val: WCHAR|mstring] ## iterator mpairs(s: var wstring): WCHAR ## ## Winim don't use built-in `WideCString`, but still support it. ## ## .. code-block:: Nim ## converter winstrConverter(s: WideCString): LPWSTR ## # WideCString can be sent to Windows API directly (unicode only). ## ## proc `+$`(s: WideCString): wstring ## # Converts WideCString to wstring. ## ## proc newWideCString(s: wstring): WideCString ## # Converts wstring to WideCString. import macros, strutils, inc/[winimbase, windef] export strutils.toHex, winimbase when not declared(IndexDefect): type IndexDefect = object of IndexError # generate from winimx (avoid importing objbase everytime) const CP_ACP = 0 CP_UTF8 = 65001 proc lstrlenA(lpString: LPCSTR): int32 {.winapi, stdcall, dynlib: "kernel32", importc.} proc lstrlenW(lpString: LPCWSTR): int32 {.winapi, stdcall, dynlib: "kernel32", importc.} proc MultiByteToWideChar(CodePage: UINT, dwFlags: DWORD, lpMultiByteStr: LPCCH, cbMultiByte: int32, lpWideCharStr: LPWSTR, cchWideChar: int32): int32 {.winapi, stdcall, dynlib: "kernel32", importc.} proc WideCharToMultiByte(CodePage: UINT, dwFlags: DWORD, lpWideCharStr: LPCWCH, cchWideChar: int32, lpMultiByteStr: LPSTR, cbMultiByte: int32, lpDefaultChar: LPCCH, lpUsedDefaultChar: LPBOOL): int32 {.winapi, stdcall, dynlib: "kernel32", importc.} proc SysStringLen(P1: BSTR): UINT {.winapi, stdcall, dynlib: "oleaut32", importc.} # helper functions proc toHex(p: pointer, L: int): string = const HexChars = "0123456789ABCDEF" let a = cast[ptr UncheckedArray[byte]](p) result = newStringOfCap(L * 2) for i in 0 ..< L: let hi = int a[i] shr 4 lo = int a[i] and 0xF result.add HexChars[hi] result.add HexChars[lo] proc toHex*(s: cstring): string {.inline.} = ## Converts a `cstring` to its hexadecimal representation. ## No prefix like ``0x`` is generated. result = toHex($s) when defined(cpu64): converter NaturalToInt32(x: Natural): int32 = int32 x # new string types type # for wstring, always add extra null wchar to ensure null-terminated. wstring* = distinct string ## New string type to store wide character string (aka. unicode string). mstring* = distinct string ## New string type to store multibyte character string (aka. ansi string). mIndex* = distinct int ## Use `mIndex` in substr, [] or []= for `mstring` means ## index by MBCS characters, not by bytes. SomeChar* = byte | char | WCHAR ## Type class matching all char types. SomeString* = string | mstring | wstring ## Type class matching all string types. SomeBuffer*[I] = ptr SomeChar | array[I, SomeChar] | ptr array[I, SomeChar] | ptr UncheckedArray[SomeChar] | openArray[SomeChar] | seq[SomeChar] ## Type class matching all string buffer types. Stringable* = SomeChar | SomeString | SomeBuffer | cstring | BSTR ## Type class matching all stringable types. template raw(s: wstring, L: Natural): var WCHAR = cast[ptr WCHAR](unsafeaddr(string(s)[L * 2]))[] template `^^`(s, i: untyped): untyped = (when i is BackwardsIndex: s.len - int(i) else: int(i)) proc newWString*(L: Natural): wstring = wstring(newString((L + 1) * 2)) ## Returns a new `wstring` of length L, counting by wide characters. proc newMString*(L: Natural): mstring = mstring(newString(L)) ## Returns a new `mstring` of length L, counting by bytes. proc len*(s: wstring): int {.inline.} = max(string(s).len div 2 - 1, 0) ## Returns the length of `wstring`, counting by wide characters. proc len*(s: mstring): int {.inline.} = string(s).len ## Returns the length of `mstring`, counting by bytes. proc newWStringOfCap*(L: Natural): wstring = ## Returns a new `wstring` of length 0 but with capacity L, counting by wide characters. result = wstring(newStringOfCap((L + 1) * 2)) string(result).add "\0\0" proc newMStringOfCap*(L: Natural): mstring = ## Returns a new `mstring` of length 0 but with capacity L, counting by bytes. result = mstring(newStringOfCap(L)) proc setLen*(s: var wstring, L: Natural) {.inline.} = ## Sets the length of `wstring` s to L, counting by wide characters. setLen(string(s), (L + 1) * 2) s.raw(s.len) = 0 proc setLen*(s: var mstring, L: Natural) {.inline.} = ## Sets the length of `mstring` s to L, counting by wide bytes. setLen(string(s), L) proc `&`*(s: string): ptr char {.inline.} = ## Get address of the first char of a `string`. result = cast[ptr char](cstring s) proc `&`*(s: cstring): ptr char {.inline.} = ## Get address of the first char of a `cstring`. result = cast[ptr char](s) proc `&`*(s: wstring): ptr WCHAR {.inline.} = ## Get address of the first WCHAR of a `wstring`. result = cast[ptr WCHAR](&(string(s))) proc `&`*(s: mstring): ptr char {.inline.} = ## Get address of the first char of a `mstring`. result = &(string(s)) proc `UTF8->wstring`(source: ptr char, L: Natural): wstring = if not source.isNil: var wLen = MultiByteToWideChar(CP_UTF8, 0, source, L, nil, 0) result = newWString(wLen) discard MultiByteToWideChar(CP_UTF8, 0, source, L, &result, wLen) proc `ANSI->wstring`(source: ptr char, L: Natural): wstring = if not source.isNil: var wLen = MultiByteToWideChar(CP_ACP, 0, source, L, nil, 0) result = newWString(wLen) discard MultiByteToWideChar(CP_ACP, 0, source, L, &result, wLen) proc `UNICODE->wstring`(source: ptr WCHAR, L: Natural): wstring = if not source.isNil: result = newWString(L) copyMem(&result, source, L * 2) proc `UTF8->mstring`(source: ptr char, L: Natural): mstring = if not source.isNil: var wLen = MultiByteToWideChar(CP_UTF8, 0, source, L, nil, 0) var buffer = cast[ptr WCHAR](alloc(wLen * 2)) if not buffer.isNil: discard MultiByteToWideChar(CP_UTF8, 0, source, L, buffer, wLen) var mLen = WideCharToMultiByte(CP_ACP, 0, buffer, wLen, nil, 0, nil, nil) result = newMString(mLen) discard WideCharToMultiByte(CP_ACP, 0, buffer, wLen, &result, mLen, nil, nil) dealloc(buffer) proc `ANSI->mstring`(source: ptr char, L: Natural): mstring = if not source.isNil: result = newMString(L) copyMem(&result, source, L) proc `UNICODE->mstring`(source: ptr WCHAR, L: Natural): mstring = if not source.isNil: var mLen = WideCharToMultiByte(CP_ACP, 0, source, L, nil, 0, nil, nil) result = newMString(mLen) discard WideCharToMultiByte(CP_ACP, 0, source, L, &result, mLen, nil, nil) proc `UTF8->string`(source: ptr char, L: Natural): string = if not source.isNil: result = newString(L) copyMem(&result, source, L) proc `ANSI->string`(source: ptr char, L: Natural): string = if not source.isNil: var wLen = MultiByteToWideChar(CP_ACP, 0, source, L, nil, 0) var buffer = cast[ptr WCHAR](alloc(wLen * 2)) if not buffer.isNil: discard MultiByteToWideChar(CP_ACP, 0, source, L, buffer, wLen) var mLen = WideCharToMultiByte(CP_UTF8, 0, buffer, wLen, nil, 0, nil, nil) result = newString(mLen) discard WideCharToMultiByte(CP_UTF8, 0, buffer, wLen, &result, mLen, nil, nil) dealloc(buffer) proc `UNICODE->string`(source: ptr WCHAR, L: Natural): string = if not source.isNil: var mLen = WideCharToMultiByte(CP_UTF8, 0, source, L, nil, 0, nil, nil) result = newString(mLen) discard WideCharToMultiByte(CP_UTF8, 0, source, L, &result, mLen, nil, nil) template getptr[T](x: openArray[T]): untyped = when sizeof(T) == 1: cast[ptr char](unsafeaddr x[0]) elif sizeof(T) == 2: cast[ptr WCHAR](unsafeaddr x[0]) template getptr[T](x: ptr UncheckedArray[T]): untyped = when sizeof(T) == 1: cast[ptr char](unsafeaddr x[0]) elif sizeof(T) == 2: cast[ptr WCHAR](unsafeaddr x[0]) proc `UTF8->wstring`(source: openArray[byte|char]): wstring {.inline.} = `UTF8->wstring`(source.getptr, source.len) proc `ANSI->wstring`(source: openArray[byte|char]): wstring {.inline.} = `ANSI->wstring`(source.getptr, source.len) proc `UNICODE->wstring`(source: openArray[WCHAR]): wstring {.inline.} = `UNICODE->wstring`(source.getptr, source.len) proc `UTF8->mstring`(source: openArray[byte|char]): mstring {.inline.} = `UTF8->mstring`(source.getptr, source.len) proc `ANSI->mstring`(source: openArray[byte|char]): mstring {.inline.} = `ANSI->mstring`(source.getptr, source.len) proc `UNICODE->mstring`(source: openArray[WCHAR]): mstring {.inline.} = `UNICODE->mstring`(source.getptr, source.len) proc `UTF8->string`(source: openArray[byte|char]): string {.inline.} = `UTF8->string`(source.getptr, source.len) proc `ANSI->string`(source: openArray[byte|char]): string {.inline.} = `ANSI->string`(source.getptr, source.len) proc `UNICODE->string`(source: openArray[WCHAR]): string {.inline.} = `UNICODE->string`(source.getptr, source.len) # wstring functions proc high*(s: wstring): int {.inline.} = s.len - 1 ## Returns the highest possible index of `wstring`. proc low*(s: wstring): int {.inline.} = 0 ## Returns the lowest possible index of `wstring`. proc cmp*(x, y: wstring): int {.borrow.} ## Compare proc for `wstring` (in binary format only). proc `==`*(x, y: wstring): bool {.borrow.} ## Checks for equality between two `wstring`. proc `<=`*(x, y: wstring): bool {.borrow.} ## Lexicographic ``<=`` operator for `wstring`. proc `<`*(x, y: wstring): bool {.borrow.} ## Lexicographic ``<`` operator for `wstring`. proc substr*(s: wstring, first, last: int): wstring = ## Copies a slice of `s` into a new `wstring` and returns it. result = wstring(string(s).substr(first * 2, last * 2 + 3)) if result.len != 0: result.raw(result.len) = 0 else: result = newWString(0) proc substr*(s: wstring, first = 0): wstring {.inline.} = ## Copies a slice of `s` into a new `wstring` and returns it. result = s.substr(first, s.high) proc `[]`*(s: wstring, i: int): WCHAR {.inline.} = ## Index operator for `wstring`. when compileOption("boundChecks"): if i >= s.len: raise newException(IndexDefect, "index out of bounds") result = s.raw(i) proc `[]=`*(s: var wstring, i: int, c: WCHAR|char) {.inline.} = ## Index assignment operator for `wstring`. when compileOption("boundChecks"): if i >= s.len: raise newException(IndexDefect, "index out of bounds") s.raw(i) = WCHAR c proc `[]`*[T, U](s: wstring, x: HSlice[T, U]): wstring = ## Slice operation for `wstring`. let a = s ^^ x.a let L = (s ^^ x.b) - a + 1 when compileOption("boundChecks"): if a < 0 or a + L > s.len: raise newException(IndexDefect, "index out of bounds") result = s.substr(a, a + L-1) proc `[]=`*[T, U](s: var wstring, x: HSlice[T, U], b: wstring) = ## Slice assignment for `wstring`. let a = s ^^ x.a let L = (s ^^ x.b) - a + 1 if L == b.len: for i in 0 ..< L: s[i+a] = b[i] else: var slen = s.len var shift = b.len - L var newLen = slen + shift if shift > 0: setLen(s, newLen) for i in countdown(newLen-1, a+shift+1): s[i] = s[i-shift] else: for i in countup(a+b.len, s.len-1+shift): s[i] = s[i-shift] setLen(s, newLen) for i in 0 ..< b.len: s[i+a] = b[i] s.raw(s.len) = 0 proc add*(s: var wstring, c: char|WCHAR) = ## Appends `c` to `s` in place. s.raw(s.len) = WCHAR c string(s).add "\0\0" proc add*(s: var wstring, u: wstring) = ## Appends `u` to `s` in place. setLen(string(s), string(s).len - 2) string(s).add(string(u)) proc `&`*(s: wstring, c: WCHAR|char): wstring {.inline.} = ## Concatenates `s` with `c`. result = s result.add c proc `&`*(s, u: wstring): wstring {.inline.} = ## Concatenates `s` with `u`. result = s result.add u proc toHex*(s: wstring): string {.inline.} = ## Converts `wstring` to its hexadecimal representation. ## No prefix like ``0x`` is generated. result = toHex(&s, s.len * 2) iterator items*(s: wstring): WCHAR = ## Iterates over each `WCHAR` of `wstring`. var i = 0 while i < s.len: yield s.raw(i) inc i iterator mitems*(s: var wstring): var WCHAR = ## Iterates over each `WCHAR` of `wstring` so that you can modify the yielded value. var i = 0 while i < s.len: yield s.raw(i) inc i iterator pairs*(s: wstring): tuple[key: int, val: WCHAR] = ## Iterates over each `WCHAR` of `wstring`. Yields `(int, WCHAR)` pairs. var i = 0 while i < s.len: yield (i, s.raw(i)) inc i iterator mpairs*(s: var wstring): tuple[key: int, val: var WCHAR] = ## Iterates over each `WCHAR` of `wstring`. Yields `(int, var WCHAR)` pairs. var i = 0 while i < s.len: yield (i, s.raw(i)) inc i proc repr*(s: wstring): string = ## Returns string representation of `wstring`. result = $cast[int](&s).tohex & "(wstring)\"" if s.len != 0: for w in s: if w == 0: result.add "\\0" else: result.add `UNICODE->string`(w.unsafeaddr, 1) result.add "\"" # mstring functions proc mlen*(s: mstring): int = ## Returns the length of `mstring`, counting by MBCS characters. result = int MultiByteToWideChar(CP_ACP, 0, &s, int32 s.len, nil, 0) proc high*(s: mstring): int {.borrow.} ## Returns the highest possible index of `mstring`. proc low*(s: mstring): int {.borrow.} ## Returns the lowest possible index of `mstring`. proc cmp*(x, y: mstring): int {.borrow.} ## Compare proc for `mstring` (in binary format only). proc `==`*(x, y: mstring): bool {.borrow.} ## Checks for equality between two `mstring`. proc `<=`*(x, y: mstring): bool {.borrow.} ## Lexicographic ``<=`` operator for `mstring`. proc `<`*(x, y: mstring): bool {.borrow.} ## Lexicographic ``<`` operator for `mstring`. proc substr*(s: mstring, first, last: int): mstring {.borrow.} ## Copies a slice of `s` into a new `mstring` and returns it, counting by bytes. proc substr*(s: mstring, first = 0): mstring {.borrow.} ## Copies a slice of `s` into a new `mstring` and returns it, counting by bytes. proc `[]`*(s: mstring, i: int): char {.inline.} = string(s)[i] ## Index operator for `mstring`, counting by bytes. proc `[]=`*(s: var mstring, i: int, x: char|byte) {.inline.} = string(s)[i] = cast[char](x) ## Index assignment operator for `mstring`, counting by bytes. proc substr*(s: mstring, first, last: mIndex): mstring = ## Copies a slice of `s` into a new `mstring` and returns it, counting by MBCS characters. var ws = `ANSI->wstring`(&s, s.len) ws = ws.substr(int first, int last) result = `UNICODE->mstring`(&ws, ws.len) proc substr*(s: mstring, first: mIndex = 0.mIndex): mstring = ## Copies a slice of `s` into a new `mstring` and returns it, counting by MBCS characters. var ws = `ANSI->wstring`(&s, s.len) ws = ws.substr(int first) result = `UNICODE->mstring`(&ws, ws.len) proc `[]`*(s: mstring, i: mIndex): mstring = ## Index operator for `mstring`, counting by MBCS characters. let ws = `ANSI->wstring`(&s, s.len) var wchar = ws[int i] result = `UNICODE->mstring`(addr wchar, 1) proc `[]=`*(s: var mstring, i: mIndex, u: mstring) = ## Index assignment operator for `mstring`, counting by MBCS characters, ## and only first MBCS characters of `u` will be used. var ws = `ANSI->wstring`(&s, s.len) let wu = `ANSI->wstring`(&u, u.len) if wu.len == 0: ws[int i] = 0 else: ws[int i] = wu[0] s = `UNICODE->mstring`(&ws, ws.len) proc `[]`*[T, U](s: mstring, x: HSlice[T, U]): mstring = ## Slice operation for `mstring`. when T is mIndex or U is mIndex: var ws = `[]`(`ANSI->wstring`(&s, s.len), x) result = `UNICODE->mstring`(&ws, ws.len) else: result = mstring(`[]`(string(s), x)) proc `[]=`*[T, U](s: var mstring, x: HSlice[T, U], u: mstring) = ## Slice assignment for `mstrings`. when T is mIndex or U is mIndex: var ws = `ANSI->wstring`(&s, s.len) `[]=`(ws, x, `ANSI->wstring`(&u, u.len)) s = `UNICODE->mstring`(&ws, ws.len) else: `[]=`(string(s), x, string(u)) proc add*(x: var mstring, y: char) {.borrow.} ## Appends `y` to `x` in place. proc add*(x: var mstring, y: string) {.borrow.} ## Appends `y` to `x` in place. proc add*(x: var mstring, y: mstring) {.borrow.} ## Appends `y` to `x` in place. proc add*(x: var mstring, y: byte) {.inline.} = x.add(char(y)) ## Appends `y` to `x` in place. proc `&`*(x: mstring, y: char): mstring {.borrow.} ## Concatenates `x` with `y`. proc `&`*(x, y: mstring): mstring {.borrow.} ## Concatenates `x` with `y`. proc `&`*(x: char, y: mstring): mstring {.borrow.} ## Concatenates `x` with `y`. proc toHex*(s: mstring): string {.inline.} = ## Converts `mstring` to its hexadecimal representation. ## No prefix like ``0x`` is generated. result = toHex(&s, s.len) iterator items*(s: mstring): mstring = ## Iterates over each MBCS character of `mstring`. var ws = `ANSI->wstring`(&s, s.len) for wchar in ws.mitems: yield `UNICODE->mstring`(addr wchar, 1) iterator pairs*(s: mstring): tuple[key: mIndex, val: mstring] = ## Iterates over each MBCS character of `mstring`. Yields `(mIndex, mstring)` pairs. var ws = `ANSI->wstring`(&s, s.len) for i, wchar in ws.mpairs: yield (mIndex i, `UNICODE->mstring`(addr wchar, 1)) proc repr*(s: mstring): string = ## Returns string representation of `mstring`. result = $cast[int](&s).tohex & "(mstring)\"" if s.len != 0: for m in s: if m[0] == '\0': result.add "\\0" else: result.add `ANSI->string`(&m, m.len) result &= "\"" # conversion functions var isOpenArrayStringable {.threadvar.}: bool proc setOpenArrayStringable*(flag: bool): bool {.inline, discardable.} = ## Nim's system.`$` will return array representation for `openArray[SomeChar]`. ## After turn this option on, winstr will overwrite the defualt behavior ## for `$` and treats `openArray[SomeChar]` as string. result = isOpenArrayStringable isOpenArrayStringable = flag proc `$`*(s: Stringable): string {.inline.} = ## Convert any stringable type to `string`. ## This operator assume `string|cstring|ptr char|openArray[char]` is utf8 encoding. when s is char|byte: system.`$`(s) elif s is WCHAR: system.`$`(s) elif s is string: s elif s is mstring: `ANSI->string`(&s, s.len) elif s is wstring: `UNICODE->string`(&s, s.len) elif s is cstring|ptr char|ptr byte: system.`$`(cast[cstring](s)) elif s is ptr WCHAR: `UNICODE->string`(s, lstrlenW(s)) elif s is BSTR: `UNICODE->string`(s, int SysStringLen(s)) elif s is array|seq|openArray: if isOpenArrayStringable: when sizeof(s[0]) == 1: `UTF8->string`(s) elif sizeof(s[0]) == 2: `UNICODE->string`(s) else: {.fatal: "invalid type".} else: system.`$`(s) elif s is ptr array: when sizeof(s[][0]) == 1: `UTF8->string`(s[]) elif sizeof(s[][0]) == 2: `UNICODE->string`(s[]) else: {.fatal: "invalid type".} elif s is ptr UncheckedArray: when sizeof(s[0]) == 1: `UTF8->string`(s.getptr, lstrlenA(s.getptr)) elif sizeof(s[0]) == 2: `UNICODE->string`(s.getptr, lstrlenW(s.getptr)) else: {.fatal: "invalid type".} else: {.fatal: "invalid type".} proc `%$`*(s: Stringable): string {.inline.} = ## Convert any stringable type to `string`. Always treat `openArray[SomeChar]` as string. ## This operator assume `string|cstring|ptr char|openArray[char]` is utf8 encoding. when s is char|byte: system.`$`(s) elif s is WCHAR: `UNICODE->string`(unsafeaddr s, 1) elif s is string: s elif s is mstring: `ANSI->string`(&s, s.len) elif s is wstring: `UNICODE->string`(&s, s.len) elif s is cstring|ptr char|ptr byte: system.`$`(cast[cstring](s)) elif s is ptr WCHAR: `UNICODE->string`(s, lstrlenW(s)) elif s is BSTR: `UNICODE->string`(s, int SysStringLen(s)) elif s is array|seq|openArray: when sizeof(s[0]) == 1: `UTF8->string`(s) elif sizeof(s[0]) == 2: `UNICODE->string`(s) else: {.fatal: "invalid type".} elif s is ptr array: when sizeof(s[][0]) == 1: `UTF8->string`(s[]) elif sizeof(s[][0]) == 2: `UNICODE->string`(s[]) else: {.fatal: "invalid type".} elif s is ptr UncheckedArray: when sizeof(s[0]) == 1: `UTF8->string`(s.getptr, lstrlenA(s.getptr)) elif sizeof(s[0]) == 2: `UNICODE->string`(s.getptr, lstrlenW(s.getptr)) else: {.fatal: "invalid type".} else: {.fatal: "invalid type".} proc `+$`*(s: Stringable): wstring {.inline.} = ## Convert any stringable type to `wstring`. ## This operator assume `string|cstring|ptr char|openArray[char]` is utf8 encoding. when s is char|byte: `UTF8->wstring`(cast[ptr char](unsafeaddr s), 1) elif s is WCHAR: `UNICODE->wstring`(unsafeaddr s, 1) elif s is string: `UTF8->wstring`(&s, s.len) elif s is mstring: `ANSI->wstring`(&s, s.len) elif s is wstring: s elif s is cstring|ptr char|ptr byte: `UTF8->wstring`(cast[ptr char](s), cast[cstring](s).len) elif s is ptr WCHAR: `UNICODE->wstring`(s, lstrlenW(s)) elif s is BSTR: `UNICODE->wstring`(s, int SysStringLen(s)) elif s is array|seq|openArray: when sizeof(s[0]) == 1: `UTF8->wstring`(s) elif sizeof(s[0]) == 2: `UNICODE->wstring`(s) else: {.fatal: "invalid type".} elif s is ptr array: when sizeof(s[][0]) == 1: `UTF8->wstring`(s[]) elif sizeof(s[][0]) == 2: `UNICODE->wstring`(s[]) else: {.fatal: "invalid type".} elif s is ptr UncheckedArray: when sizeof(s[0]) == 1: `UTF8->wstring`(s.getptr, lstrlenA(s.getptr)) elif sizeof(s[0]) == 2: `UNICODE->wstring`(s.getptr, lstrlenW(s.getptr)) else: {.fatal: "invalid type".} else: {.fatal: "invalid type".} proc `-$`*(s: Stringable): mstring {.inline.} = ## Convert any stringable type to `mstring`. ## This operator assume `string|cstring|ptr char|openArray[char]` is utf8 encoding. when s is char|byte: `UTF8->mstring`(cast[ptr char](unsafeaddr s), 1) elif s is WCHAR: `UNICODE->mstring`(unsafeaddr s, 1) elif s is string: `UTF8->mstring`(&s, s.len) elif s is mstring: s elif s is wstring: `UNICODE->mstring`(&s, s.len) elif s is cstring|ptr char|ptr byte: `UTF8->mstring`(cast[ptr char](s), cast[cstring](s).len) elif s is ptr WCHAR: `UNICODE->mstring`(s, lstrlenW(s)) elif s is BSTR: `UNICODE->mstring`(s, int SysStringLen(s)) elif s is array|seq|openArray: when sizeof(s[0]) == 1: `UTF8->mstring`(s) elif sizeof(s[0]) == 2: `UNICODE->mstring`(s) else: {.fatal: "invalid type".} elif s is ptr array: when sizeof(s[][0]) == 1: `UTF8->mstring`(s[]) elif sizeof(s[][0]) == 2: `UNICODE->mstring`(s[]) else: {.fatal: "invalid type".} elif s is ptr UncheckedArray: when sizeof(s[0]) == 1: `UTF8->mstring`(s.getptr, lstrlenA(s.getptr)) elif sizeof(s[0]) == 2: `UNICODE->mstring`(s.getptr, lstrlenW(s.getptr)) else: {.fatal: "invalid type".} else: {.fatal: "invalid type".} proc `$$`*(s: Stringable): string {.inline.} = ## Convert any stringable type to `string`. ## This operator assume `string|cstring|ptr char|openArray[char]` is ansi encoding. # Only exception is `WCHAR(uint16)`: regard as number in `$`, but string in `$$` when s is WCHAR: `UNICODE->string`(unsafeaddr s, 1) elif s is string: `ANSI->string`(&s, s.len) elif s is cstring|ptr char|ptr byte: `ANSI->string`(cast[ptr char](s), cast[cstring](s).len) elif s is array|seq|openArray: when sizeof(s[0]) == 1: `ANSI->string`(s) elif sizeof(s[0]) == 2: `UNICODE->string`(s) elif s is ptr array: when sizeof(s[][0]) == 1: `ANSI->string`(s[]) else: `$`(s) elif s is ptr UncheckedArray: when sizeof(s[0]) == 1: `ANSI->string`(s.getptr, lstrlenA(s.getptr)) else: `$`(s) else: `$`(s) proc `+$$`*(s: Stringable): wstring {.inline.} = ## Convert any stringable type to `wstring`. ## This operator assume `string|cstring|ptr char|openArray[char]` is ansi encoding. when s is string: `ANSI->wstring`(&s, s.len) elif s is cstring|ptr char|ptr byte: `ANSI->wstring`(cast[ptr char](s), cast[cstring](s).len) elif s is array|seq|openArray: when sizeof(s[0]) == 1: `ANSI->wstring`(s) else: `+$`(s) elif s is ptr array: when sizeof(s[][0]) == 1: `ANSI->wstring`(s[]) else: `+$`(s) elif s is ptr UncheckedArray: when sizeof(s[0]) == 1: `ANSI->wstring`(s.getptr, lstrlenA(s.getptr)) else: `+$`(s) else: `+$`(s) proc `-$$`*(s: Stringable): mstring {.inline.} = ## Convert any stringable type to `mstring`. ## This operator assume `string|cstring|ptr char|openArray[char]` is ansi encoding. when s is string: `ANSI->mstring`(&s, s.len) elif s is cstring|ptr char|ptr byte: `ANSI->mstring`(cast[ptr char](s), cast[cstring](s).len) elif s is array|seq|openArray: when sizeof(s[0]) == 1: `ANSI->mstring`(s) else: `-$`(s) elif s is ptr array: when sizeof(s[][0]) == 1: `ANSI->mstring`(s[]) else: `-$`(s) elif s is ptr UncheckedArray: when sizeof(s[0]) == 1: `ANSI->mstring`(s.getptr, lstrlenA(s.getptr)) else: `-$`(s) else: `-$`(s) proc fillBuffer[T: SomeChar](a: var openArray[T], s: SomeString, skip = 0, inclNull = false) = when sizeof(a[0]) != sizeof(s[0]): {.fatal: "type mismatch".} for i in 0 .. min(a.high, s.len - skip - 1): a[i] = cast[T](s[i + skip]) if inclNull and a.high >= s.len: a[s.len - skip] = cast[T](0) # fill as much as possible before raise an exception if (not inclNull and a.high < s.len - skip - 1) or (inclNull and a.high < s.len - skip): raise newException(IndexDefect, "string length too long") proc fillString[T: SomeChar](s: var SomeString, a: openArray[T], skip = 0) = when sizeof(a[0]) != sizeof(s[0]): {.fatal: "type mismatch".} for i in 0 .. min(a.high, s.len - skip - 1): s[i + skip] = cast[s[0].type](a[i]) template `<<`*[A: SomeBuffer|SomeString, B: SomeBuffer|SomeString](a: A, b: B) = ## Fill operator for `SomeBuffer` and `SomeString`. ## Please make sure both side have the same character size. ## If destination don't have the length information (e.g. `pointer` or `UncheckedArray`), ## please make sure the buffer size is large enough. when A is SomeString and B is SomeString: # treat string a as buffer var v = cast[ptr UncheckedArray[a[0].type]](&a) fillBuffer(v.toOpenArray(0, a.len - 1), b, inclNull=false) elif A is not SomeString and B is SomeString: when A is array: fillBuffer(a.toOpenArray(a.low, a.high), b, skip=a.low, inclNull=false) elif A is ptr array: fillBuffer(a[].toOpenArray(a[].low, a[].high), b, skip=a[].low, inclNull=false) elif A is ptr char | ptr byte: var v = cast[ptr UncheckedArray[byte]](a) fillBuffer(v.toOpenArray(0, int.high - 1), b, inclNull=false) elif A is ptr WCHAR: var v = cast[ptr UncheckedArray[WCHAR]](a) fillBuffer(v.toOpenArray(0, int.high - 1), b, inclNull=false) elif A is ptr UncheckedArray: var v = a fillBuffer(v.toOpenArray(0, int.high - 1), b, inclNull=false) elif A is openArray | seq: fillBuffer(a, b, inclNull=false) else: {.fatal: "type mismatch".} elif A is SomeString and B is not SomeString: when B is array: fillString(a, b.toOpenArray(b.low, b.high), skip=b.low) elif B is ptr array: fillString(a, b[].toOpenArray(b[].low, b[].high), skip=b[].low) elif B is ptr char | ptr byte: fillString(a, cast[ptr UncheckedArray[byte]](b).toOpenArray(0, int.high - 1)) elif B is ptr WCHAR: fillString(a, cast[ptr UncheckedArray[WCHAR]](b).toOpenArray(0, int.high - 1)) elif B is ptr UncheckedArray: fillString(a, b.toOpenArray(0, int.high - 1)) elif B is openArray | seq: fillString(a, b) else: {.fatal: "type mismatch".} else: {.fatal: "type mismatch".} template `<<<`*[A: SomeBuffer|SomeString](a: A, b: SomeString) = ## Fill buffer by string, include a null. ## Please make sure both side have the same character size. ## If destination don't have the length information (e.g. `pointer` or `UncheckedArray`), ## please make sure the buffer size is large enough. when A is SomeString: # treat string a as buffer var v = cast[ptr UncheckedArray[a[0].type]](&a) fillBuffer(v.toOpenArray(0, a.len - 1), b, inclNull=true) else: when A is array: fillBuffer(a.toOpenArray(a.low, a.high), b, skip=a.low, inclNull=true) elif A is ptr array: fillBuffer(a[].toOpenArray(a[].low, a[].high), b, skip=a[].low, inclNull=true) elif A is ptr char | ptr byte: var v = cast[ptr UncheckedArray[byte]](a) fillBuffer(v.toOpenArray(0, int.high - 1), b, inclNull=true) elif A is ptr WCHAR: var v = cast[ptr UncheckedArray[WCHAR]](a) fillBuffer(v.toOpenArray(0, int.high - 1), b, inclNull=true) elif A is ptr UncheckedArray: var v = a fillBuffer(v.toOpenArray(0, int.high - 1), b, inclNull=true) elif A is openArray | seq: fillBuffer(a, b, inclNull=true) else: {.fatal: "type mismatch".} template `>>`*(a: typed, b: typed) = ## This is the same as `b << a`. b << a template `>>>`*(a: typed, b: typed) = ## This is the same as `b <<< a`. b <<< a proc nullTerminate*(s: var SomeString) {.inline.} = ## Assume a string is null terminated and set the correct length. when s is string|mstring: let L = lstrlenA(cast[LPCSTR](&s)) if L < s.len: s.setLen(L) elif s is wstring: let L = lstrlenW(cast[LPWSTR](&s)) if L < s.len: s.setLen(L) else: {.fatal: "invalid type".} proc nullTerminated*[T: SomeString](s: T): T {.inline.} = ## Assume a string is null terminated and return the length-corrected string. when s is string: result = newString(lstrlenA(cast[LPCSTR](&s))) result << &s elif s is mstring: result = newMString(lstrlenA(cast[LPCSTR](&s))) result << &s elif s is wstring: result = newWString(lstrlenW(cast[LPWSTR](&s))) result << &s else: {.fatal: "invalid type".} # generics has problems on converters, define one by one converter winstrConverterWStringToLPWSTR*(x: wstring): LPWSTR = cast[LPWSTR](&x) ## Converts `wstring` to `LPWSTR` automatically. converter winstrConverterWStringToBSTR*(x: wstring): BSTR = cast[BSTR](&x) ## Converts `wstring` to `BSTR` automatically. converter winstrConverterBSTRToLPWSTR*(x: BSTR): LPWSTR = cast[LPWSTR](x) ## Converts `BSTR` to `LPWSTR` automatically. converter winstrConverterStringToPtrChar*(x: string): ptr char = cast[ptr char](&x) ## Converts `string` to `ptr char` automatically. converter winstrConverterCStringToPtrChar*(x: cstring): ptr char = cast[ptr char](x) ## Converts `cstring` to `ptr char` automatically. converter winstrConverterMStringToPtrChar*(x: mstring): ptr char = cast[ptr char](&x) ## Converts `mstring` to `ptr char` automatically. converter winstrConverterMStringToLPSTR*(x: mstring): LPSTR = cast[LPSTR](&x) ## Converts `mstring` to `LPSTR` automatically. when defined(gcDestructors): converter winstrConverterWideCStringToLPWSTR*(x: WideCStringObj): LPWSTR = cast[LPWSTR](x[0].unsafeaddr) ## Converts `WideCString` to `LPWSTR` automatically. proc `+$`*(s: WideCStringObj): wstring {.inline.} = ## Converts `WideCString` to `wstring`. +$cast[LPWSTR](s[0].unsafeaddr) proc newWideCString*(s: wstring): WideCStringObj {.inline.} = ## Converts `wstring` to `WideCString`. when compiles(newWideCString(1)): result = newWideCString(s.len) copyMem(result[0].unsafeaddr, &s, s.len * 2) else: result = newWideCString("", s.len) s >> result else: converter winstrConverterWideCStringToLPWSTR*(x: WideCString): LPWSTR = cast[LPWSTR](x[0].unsafeaddr) ## Converts `WideCString` to `LPWSTR` automatically. proc `+$`*(s: WideCString): wstring {.inline.} = ## Converts `WideCString` to `wstring`. +$cast[LPWSTR](s[0].unsafeaddr) proc newWideCString*(s: wstring): WideCString {.inline.} = ## Converts `wstring` to `WideCString`. when compiles(newWideCString(1)): result = newWideCString(s.len) copyMem(result[0].unsafeaddr, &s, s.len * 2) else: result = newWideCString("", s.len) s >> result when defined(gcDestructors): # Here is the workaround for --gc:arc and --newruntime. It is a tricky problem, # wstring needs to be alive until converter ending so that the windows API can # use the pointer later. import deques var wstringQueue {.threadvar.}: Deque[wstring] var wstringQueueInit {.threadvar.}: bool proc saddr(str: sink wstring): LPWSTR = if not wstringQueueInit: wstringQueue = initDeque[wstring]() wstringQueueInit = true elif wstringQueue.len > 128: wstringQueue.shrink(fromFirst=64) wstringQueue.addLast str &wstringQueue[^1] converter winstrConverterStringToLPWSTR*(x: string): LPWSTR = saddr(+$x) ## Converts `string` to `LPWSTR` automatically. converter winstrConverterCStringToLPWSTR*(x: cstring): LPWSTR = saddr(+$x) ## Converts `cstring` to `LPWSTR` automatically. converter winstrConverterStringToBSTR*(x: string): BSTR = cast[BSTR](saddr(+$x)) ## Converts `string` to `BSTR` automatically. converter winstrConverterCStringToBSTR*(x: cstring): BSTR = cast[BSTR](saddr(+$x)) ## Converts `cstring` to `BSTR` automatically. else: converter winstrConverterStringToLPWSTR*(x: string): LPWSTR = cast[LPWSTR](&(+$x)) ## Converts `string` to `LPWSTR` automatically. converter winstrConverterCStringToLPWSTR*(x: cstring): LPWSTR = cast[LPWSTR](&(+$x)) ## Converts `cstring` to `LPWSTR` automatically. converter winstrConverterStringToBSTR*(x: string): BSTR = cast[BSTR](&(+$x)) ## Converts `string` to `BSTR` automatically. converter winstrConverterCStringToBSTR*(x: cstring): BSTR = cast[BSTR](&(+$x)) ## Converts `cstring` to `BSTR` automatically. proc newWString*(s: cstring|string|mstring): wstring {.inline, deprecated: "use `+$` instead".} = ## Return a new `wstring`. result = +$s proc newMString*(s: string|cstring|wstring): mstring {.inline, deprecated: "use `-$` instead".} = ## Return a new `mstring`. result = -$s proc ctNewWString(s: static[string]): wstring {.compiletime.} = # copy from widestrs.nim, use WCHAR instead of Utf16Char const UNI_REPLACEMENT_CHAR = WCHAR(0xFFFD'u16) UNI_MAX_BMP = 0x0000FFFF UNI_MAX_UTF16 = 0x0010FFFF halfShift = 10 halfBase = 0x0010000 halfMask = 0x3FF UNI_SUR_HIGH_START = 0xD800 UNI_SUR_LOW_START = 0xDC00 UNI_SUR_LOW_END = 0xDFFF UNI_REPL = 0xFFFD template ones(n: untyped): untyped = ((1 shl n)-1) template fastRuneAt(s: cstring, i, L: int, result: untyped, doInc = true) = if ord(s[i]) <= 127: result = ord(s[i]) when doInc: inc(i) elif ord(s[i]) shr 5 == 0b110: if i <= L - 2: result = (ord(s[i]) and (ones(5))) shl 6 or (ord(s[i+1]) and ones(6)) when doInc: inc(i, 2) else: result = UNI_REPL when doInc: inc(i) elif ord(s[i]) shr 4 == 0b1110: if i <= L - 3: result = (ord(s[i]) and ones(4)) shl 12 or (ord(s[i+1]) and ones(6)) shl 6 or (ord(s[i+2]) and ones(6)) when doInc: inc(i, 3) else: result = UNI_REPL when doInc: inc(i) elif ord(s[i]) shr 3 == 0b11110: if i <= L - 4: result = (ord(s[i]) and ones(3)) shl 18 or (ord(s[i+1]) and ones(6)) shl 12 or (ord(s[i+2]) and ones(6)) shl 6 or (ord(s[i+3]) and ones(6)) when doInc: inc(i, 4) else: result = UNI_REPL when doInc: inc(i) else: result = 0xFFFD when doInc: inc(i) iterator runes(s: cstring, L: int): int = var i = 0 ret: int while i < L: fastRuneAt(s, i, L, ret, true) yield ret iterator WCHARs(source: cstring, L: int): WCHAR = for ch in runes(source, L): if ch <=% UNI_MAX_BMP: if ch >=% UNI_SUR_HIGH_START and ch <=% UNI_SUR_LOW_END: yield UNI_REPLACEMENT_CHAR else: yield WCHAR(ch) elif ch >% UNI_MAX_UTF16: yield UNI_REPLACEMENT_CHAR else: let ch = ch -% halfBase yield WCHAR((ch shr halfShift) +% UNI_SUR_HIGH_START) yield WCHAR((ch and halfMask) +% UNI_SUR_LOW_START) var ret: string for u in WCHARs(s, s.len): ret.add char(u and 0xFF) ret.add char(u shr 8) ret.add "\0\0" result = wstring ret template L*(x: static[string]): wstring = ## Generate const wstring from `static[string]` at compile-time. const wstr = ctNewWString(x) wstr template L*(x: string): wstring = +$x ## Same as `+$` for dynamic string (string at run-time). template T*(x: string): untyped = ## Generate wstring or mstring depend on conditional symbol: `useWinAnsi`. # must export winimbase to use winimAnsi here. when winimAnsi: -$x else: L(x) template T*(x: Natural): untyped = ## Generate wstring or mstring buffer depend on conditional symbol: `useWinAnsi`. ## Use `&` to get the buffer address and then pass to Windows API. when winimAnsi: newMString(x) else: newWString(x) when winimAnsi: type TString* = mstring ## `wstring` or `mstring` depend on conditional symbol: `useWinAnsi`. else: type TString* = wstring ## `wstring` or `mstring` depend on conditional symbol: `useWinAnsi`. when isMainModule: let str = "the quick brown fox jumps over the lazy dog" echo +$str == L"the quick brown fox jumps over the lazy dog"