strs_v2.nim 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2017 Nim contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Default new string implementation used by Nim's core.
  10. type
  11. NimStrPayloadBase = object
  12. cap: int
  13. NimStrPayload {.core.} = object
  14. cap: int
  15. data: UncheckedArray[char]
  16. NimStringV2 {.core.} = object
  17. len: int
  18. p: ptr NimStrPayload ## can be nil if len == 0.
  19. const nimStrVersion {.core.} = 2
  20. template isLiteral(s): bool = (s.p == nil) or (s.p.cap and strlitFlag) == strlitFlag
  21. template contentSize(cap): int = cap + 1 + sizeof(NimStrPayloadBase)
  22. template frees(s) =
  23. if not isLiteral(s):
  24. when compileOption("threads"):
  25. deallocShared(s.p)
  26. else:
  27. dealloc(s.p)
  28. template allocPayload(newLen: int): ptr NimStrPayload =
  29. when compileOption("threads"):
  30. cast[ptr NimStrPayload](allocShared(contentSize(newLen)))
  31. else:
  32. cast[ptr NimStrPayload](alloc(contentSize(newLen)))
  33. template allocPayload0(newLen: int): ptr NimStrPayload =
  34. when compileOption("threads"):
  35. cast[ptr NimStrPayload](allocShared0(contentSize(newLen)))
  36. else:
  37. cast[ptr NimStrPayload](alloc0(contentSize(newLen)))
  38. template reallocPayload(p: pointer, newLen: int): ptr NimStrPayload =
  39. when compileOption("threads"):
  40. cast[ptr NimStrPayload](reallocShared(p, contentSize(newLen)))
  41. else:
  42. cast[ptr NimStrPayload](realloc(p, contentSize(newLen)))
  43. template reallocPayload0(p: pointer; oldLen, newLen: int): ptr NimStrPayload =
  44. when compileOption("threads"):
  45. cast[ptr NimStrPayload](reallocShared0(p, contentSize(oldLen), contentSize(newLen)))
  46. else:
  47. cast[ptr NimStrPayload](realloc0(p, contentSize(oldLen), contentSize(newLen)))
  48. proc resize(old: int): int {.inline.} =
  49. if old <= 0: result = 4
  50. elif old <= high(int16): result = old * 2
  51. else: result = old * 3 div 2 # for large arrays * 3/2 is better
  52. proc prepareAdd(s: var NimStringV2; addLen: int) {.compilerRtl.} =
  53. let newLen = s.len + addLen
  54. if isLiteral(s):
  55. let oldP = s.p
  56. # can't mutate a literal, so we need a fresh copy here:
  57. s.p = allocPayload(newLen)
  58. s.p.cap = newLen
  59. if s.len > 0:
  60. # we are about to append, so there is no need to copy the \0 terminator:
  61. copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], min(s.len, newLen))
  62. elif oldP == nil:
  63. # In the case of `newString(0) & ""`, since `src.len == 0`, `appendString`
  64. # will not set the `\0` terminator, so we set it here.
  65. s.p.data[0] = '\0'
  66. else:
  67. let oldCap = s.p.cap and not strlitFlag
  68. if newLen > oldCap:
  69. let newCap = max(newLen, resize(oldCap))
  70. s.p = reallocPayload(s.p, newCap)
  71. s.p.cap = newCap
  72. if newLen < newCap:
  73. zeroMem(cast[pointer](addr s.p.data[newLen+1]), newCap - newLen)
  74. proc nimAddCharV1(s: var NimStringV2; c: char) {.compilerRtl, inl.} =
  75. #if (s.p == nil) or (s.len+1 > s.p.cap and not strlitFlag):
  76. prepareAdd(s, 1)
  77. s.p.data[s.len] = c
  78. inc s.len
  79. s.p.data[s.len] = '\0'
  80. proc toNimStr(str: cstring, len: int): NimStringV2 {.compilerproc.} =
  81. if len <= 0:
  82. result = NimStringV2(len: 0, p: nil)
  83. else:
  84. var p = allocPayload(len)
  85. p.cap = len
  86. copyMem(unsafeAddr p.data[0], str, len+1)
  87. result = NimStringV2(len: len, p: p)
  88. proc cstrToNimstr(str: cstring): NimStringV2 {.compilerRtl.} =
  89. if str == nil: toNimStr(str, 0)
  90. else: toNimStr(str, str.len)
  91. proc nimToCStringConv(s: NimStringV2): cstring {.compilerproc, nonReloadable, inline.} =
  92. if s.len == 0: result = cstring""
  93. else: result = cast[cstring](unsafeAddr s.p.data)
  94. proc appendString(dest: var NimStringV2; src: NimStringV2) {.compilerproc, inline.} =
  95. if src.len > 0:
  96. # also copy the \0 terminator:
  97. copyMem(unsafeAddr dest.p.data[dest.len], unsafeAddr src.p.data[0], src.len+1)
  98. inc dest.len, src.len
  99. proc appendChar(dest: var NimStringV2; c: char) {.compilerproc, inline.} =
  100. dest.p.data[dest.len] = c
  101. inc dest.len
  102. dest.p.data[dest.len] = '\0'
  103. proc rawNewString(space: int): NimStringV2 {.compilerproc.} =
  104. # this is also 'system.newStringOfCap'.
  105. if space <= 0:
  106. result = NimStringV2(len: 0, p: nil)
  107. else:
  108. var p = allocPayload(space)
  109. p.cap = space
  110. p.data[0] = '\0'
  111. result = NimStringV2(len: 0, p: p)
  112. proc mnewString(len: int): NimStringV2 {.compilerproc.} =
  113. if len <= 0:
  114. result = NimStringV2(len: 0, p: nil)
  115. else:
  116. var p = allocPayload0(len)
  117. p.cap = len
  118. result = NimStringV2(len: len, p: p)
  119. proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} =
  120. if newLen == 0:
  121. discard "do not free the buffer here, pattern 's.setLen 0' is common for avoiding allocations"
  122. else:
  123. if isLiteral(s):
  124. let oldP = s.p
  125. s.p = allocPayload(newLen)
  126. s.p.cap = newLen
  127. if s.len > 0:
  128. copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], min(s.len, newLen))
  129. if newLen > s.len:
  130. zeroMem(cast[pointer](addr s.p.data[s.len]), newLen - s.len + 1)
  131. else:
  132. s.p.data[newLen] = '\0'
  133. else:
  134. zeroMem(cast[pointer](addr s.p.data[0]), newLen + 1)
  135. elif newLen > s.len:
  136. let oldCap = s.p.cap and not strlitFlag
  137. if newLen > oldCap:
  138. let newCap = max(newLen, resize(oldCap))
  139. s.p = reallocPayload0(s.p, oldCap, newCap)
  140. s.p.cap = newCap
  141. s.p.data[newLen] = '\0'
  142. s.len = newLen
  143. proc nimAsgnStrV2(a: var NimStringV2, b: NimStringV2) {.compilerRtl.} =
  144. if a.p == b.p: return
  145. if isLiteral(b):
  146. # we can shallow copy literals:
  147. frees(a)
  148. a.len = b.len
  149. a.p = b.p
  150. else:
  151. if isLiteral(a) or (a.p.cap and not strlitFlag) < b.len:
  152. # we have to allocate the 'cap' here, consider
  153. # 'let y = newStringOfCap(); var x = y'
  154. # on the other hand... These get turned into moves now.
  155. frees(a)
  156. a.p = allocPayload(b.len)
  157. a.p.cap = b.len
  158. a.len = b.len
  159. copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], b.len+1)
  160. proc nimPrepareStrMutationImpl(s: var NimStringV2) =
  161. let oldP = s.p
  162. # can't mutate a literal, so we need a fresh copy here:
  163. s.p = allocPayload(s.len)
  164. s.p.cap = s.len
  165. copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], s.len+1)
  166. proc nimPrepareStrMutationV2(s: var NimStringV2) {.compilerRtl, inl.} =
  167. if s.p != nil and (s.p.cap and strlitFlag) == strlitFlag:
  168. nimPrepareStrMutationImpl(s)
  169. proc prepareMutation*(s: var string) {.inline.} =
  170. # string literals are "copy on write", so you need to call
  171. # `prepareMutation` before modifying the strings via `addr`.
  172. {.cast(noSideEffect).}:
  173. let s = unsafeAddr s
  174. nimPrepareStrMutationV2(cast[ptr NimStringV2](s)[])
  175. proc nimAddStrV1(s: var NimStringV2; src: NimStringV2) {.compilerRtl, inl.} =
  176. #if (s.p == nil) or (s.len+1 > s.p.cap and not strlitFlag):
  177. prepareAdd(s, src.len)
  178. appendString s, src
  179. proc nimDestroyStrV1(s: NimStringV2) {.compilerRtl, inl.} =
  180. frees(s)
  181. proc nimStrAtLe(s: string; idx: int; ch: char): bool {.compilerRtl, inl.} =
  182. result = idx < s.len and s[idx] <= ch
  183. func capacity*(self: string): int {.inline.} =
  184. ## Returns the current capacity of the string.
  185. # See https://github.com/nim-lang/RFCs/issues/460
  186. runnableExamples:
  187. var str = newStringOfCap(cap = 42)
  188. str.add "Nim"
  189. assert str.capacity == 42
  190. let str = cast[ptr NimStringV2](unsafeAddr self)
  191. result = if str.p != nil: str.p.cap and not strlitFlag else: 0