strs_v2.nim 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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. proc resize(old: int): int {.inline.} =
  29. if old <= 0: result = 4
  30. elif old <= high(int16): result = old * 2
  31. else: result = old * 3 div 2 # for large arrays * 3/2 is better
  32. proc prepareAdd(s: var NimStringV2; addlen: int) {.compilerRtl.} =
  33. let newLen = s.len + addlen
  34. if isLiteral(s):
  35. let oldP = s.p
  36. # can't mutate a literal, so we need a fresh copy here:
  37. when compileOption("threads"):
  38. s.p = cast[ptr NimStrPayload](allocShared0(contentSize(newLen)))
  39. else:
  40. s.p = cast[ptr NimStrPayload](alloc0(contentSize(newLen)))
  41. s.p.cap = newLen
  42. if s.len > 0:
  43. # we are about to append, so there is no need to copy the \0 terminator:
  44. copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], min(s.len, newLen))
  45. else:
  46. let oldCap = s.p.cap and not strlitFlag
  47. if newLen > oldCap:
  48. let newCap = max(newLen, resize(oldCap))
  49. when compileOption("threads"):
  50. s.p = cast[ptr NimStrPayload](reallocShared0(s.p, contentSize(oldCap), contentSize(newCap)))
  51. else:
  52. s.p = cast[ptr NimStrPayload](realloc0(s.p, contentSize(oldCap), contentSize(newCap)))
  53. s.p.cap = newCap
  54. proc nimAddCharV1(s: var NimStringV2; c: char) {.compilerRtl, inline.} =
  55. #if (s.p == nil) or (s.len+1 > s.p.cap and not strlitFlag):
  56. prepareAdd(s, 1)
  57. s.p.data[s.len] = c
  58. s.p.data[s.len+1] = '\0'
  59. inc s.len
  60. proc toNimStr(str: cstring, len: int): NimStringV2 {.compilerproc.} =
  61. if len <= 0:
  62. result = NimStringV2(len: 0, p: nil)
  63. else:
  64. when compileOption("threads"):
  65. var p = cast[ptr NimStrPayload](allocShared0(contentSize(len)))
  66. else:
  67. var p = cast[ptr NimStrPayload](alloc0(contentSize(len)))
  68. p.cap = len
  69. if len > 0:
  70. # we are about to append, so there is no need to copy the \0 terminator:
  71. copyMem(unsafeAddr p.data[0], str, len)
  72. result = NimStringV2(len: len, p: p)
  73. proc cstrToNimstr(str: cstring): NimStringV2 {.compilerRtl.} =
  74. if str == nil: toNimStr(str, 0)
  75. else: toNimStr(str, str.len)
  76. proc nimToCStringConv(s: NimStringV2): cstring {.compilerproc, nonReloadable, inline.} =
  77. if s.len == 0: result = cstring""
  78. else: result = cast[cstring](unsafeAddr s.p.data)
  79. proc appendString(dest: var NimStringV2; src: NimStringV2) {.compilerproc, inline.} =
  80. if src.len > 0:
  81. # also copy the \0 terminator:
  82. copyMem(unsafeAddr dest.p.data[dest.len], unsafeAddr src.p.data[0], src.len+1)
  83. inc dest.len, src.len
  84. proc appendChar(dest: var NimStringV2; c: char) {.compilerproc, inline.} =
  85. dest.p.data[dest.len] = c
  86. dest.p.data[dest.len+1] = '\0'
  87. inc dest.len
  88. proc rawNewString(space: int): NimStringV2 {.compilerproc.} =
  89. # this is also 'system.newStringOfCap'.
  90. if space <= 0:
  91. result = NimStringV2(len: 0, p: nil)
  92. else:
  93. when compileOption("threads"):
  94. var p = cast[ptr NimStrPayload](allocShared0(contentSize(space)))
  95. else:
  96. var p = cast[ptr NimStrPayload](alloc0(contentSize(space)))
  97. p.cap = space
  98. result = NimStringV2(len: 0, p: p)
  99. proc mnewString(len: int): NimStringV2 {.compilerproc.} =
  100. if len <= 0:
  101. result = NimStringV2(len: 0, p: nil)
  102. else:
  103. when compileOption("threads"):
  104. var p = cast[ptr NimStrPayload](allocShared0(contentSize(len)))
  105. else:
  106. var p = cast[ptr NimStrPayload](alloc0(contentSize(len)))
  107. p.cap = len
  108. result = NimStringV2(len: len, p: p)
  109. proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} =
  110. if newLen == 0:
  111. discard "do not free the buffer here, pattern 's.setLen 0' is common for avoiding allocations"
  112. else:
  113. if newLen > s.len or isLiteral(s):
  114. prepareAdd(s, newLen - s.len)
  115. s.p.data[newLen] = '\0'
  116. s.len = newLen
  117. proc nimAsgnStrV2(a: var NimStringV2, b: NimStringV2) {.compilerRtl.} =
  118. if a.p == b.p: return
  119. if isLiteral(b):
  120. # we can shallow copy literals:
  121. frees(a)
  122. a.len = b.len
  123. a.p = b.p
  124. else:
  125. if isLiteral(a) or (a.p.cap and not strlitFlag) < b.len:
  126. # we have to allocate the 'cap' here, consider
  127. # 'let y = newStringOfCap(); var x = y'
  128. # on the other hand... These get turned into moves now.
  129. frees(a)
  130. when compileOption("threads"):
  131. a.p = cast[ptr NimStrPayload](allocShared0(contentSize(b.len)))
  132. else:
  133. a.p = cast[ptr NimStrPayload](alloc0(contentSize(b.len)))
  134. a.p.cap = b.len
  135. a.len = b.len
  136. copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], b.len+1)
  137. proc nimPrepareStrMutationImpl(s: var NimStringV2) =
  138. let oldP = s.p
  139. # can't mutate a literal, so we need a fresh copy here:
  140. when compileOption("threads"):
  141. s.p = cast[ptr NimStrPayload](allocShared0(contentSize(s.len)))
  142. else:
  143. s.p = cast[ptr NimStrPayload](alloc0(contentSize(s.len)))
  144. s.p.cap = s.len
  145. copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], s.len+1)
  146. proc nimPrepareStrMutationV2(s: var NimStringV2) {.compilerRtl, inline.} =
  147. if s.p != nil and (s.p.cap and strlitFlag) == strlitFlag:
  148. nimPrepareStrMutationImpl(s)
  149. proc prepareMutation*(s: var string) {.inline.} =
  150. # string literals are "copy on write", so you need to call
  151. # `prepareMutation` before modifying the strings via `addr`.
  152. {.cast(noSideEffect).}:
  153. let s = unsafeAddr s
  154. nimPrepareStrMutationV2(cast[ptr NimStringV2](s)[])
  155. template capacityImpl(str: NimStringV2): int =
  156. if str.p != nil: str.p.cap else: 0
  157. func capacity*(self: string): int {.inline.} =
  158. ## Returns the current capacity of the string.
  159. # See https://github.com/nim-lang/RFCs/issues/460
  160. runnableExamples:
  161. var str = newStringOfCap(cap = 42)
  162. str.add "Nim"
  163. assert str.capacity == 42
  164. {.cast(noSideEffect).}:
  165. let str = unsafeAddr self
  166. result = capacityImpl(cast[ptr NimStringV2](str)[])