#
#
#            Nim's Runtime Library
#        (c) Copyright 2017 Nim contributors
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Default string implementation used by Nim's core.

import allocators

type
  string {.core.} = object
    len, cap: int
    data: ptr UncheckedArray[char]

proc nimStringLiteral(x: cstring; len: int): string {.core.} =
  string(len: len, cap: len, data: x)

template frees(s) = dealloc(s.data, s.cap + 1)

proc `=destroy`(s: var string) =
  if s.data != nil:
    frees(s)
    s.data = nil
    s.len = 0
    s.cap = 0

proc `=sink`(a: var string, b: string) =
  # we hope this is optimized away for not yet alive objects:
  if a.data != nil and a.data != b.data:
    frees(a)
  a.len = b.len
  a.cap = b.cap
  a.data = b.data

proc `=`(a: var string; b: string) =
  if a.data != nil and a.data != b.data:
    frees(a)
    a.data = nil
  a.len = b.len
  a.cap = b.cap
  if b.data != nil:
    a.data = cast[type(a.data)](alloc(a.cap + 1))
    copyMem(a.data, b.data, a.cap+1)

proc resize(s: var string) =
  let old = s.cap
  if old == 0: s.cap = 8
  else: s.cap = (s.cap * 3) shr 1
  s.data = cast[type(s.data)](realloc(s.data, old + 1, s.cap + 1))

proc add*(s: var string; c: char) =
  if s.len >= s.cap: resize(s)
  s.data[s.len] = c
  s.data[s.len+1] = '\0'
  inc s.len

proc ensure(s: var string; newLen: int) =
  let old = s.cap
  if newLen >= old:
    s.cap = max((old * 3) shr 1, newLen)
    if s.cap > 0:
      s.data = cast[type(s.data)](realloc(s.data, old + 1, s.cap + 1))

proc add*(s: var string; y: string) =
  if y.len != 0:
    let newLen = s.len + y.len
    ensure(s, newLen)
    copyMem(addr s.data[len], y.data, y.data.len + 1)
    s.len = newLen

proc len*(s: string): int {.inline.} = s.len

proc newString*(len: int): string =
  result.len = len
  result.cap = len
  if len > 0:
    result.data = alloc0(len+1)

converter toCString(x: string): cstring {.core.} =
  if x.len == 0: cstring"" else: cast[cstring](x.data)

proc newStringOfCap*(cap: int): string =
  result.len = 0
  result.cap = cap
  if cap > 0:
    result.data = alloc(cap+1)

proc `&`*(a, b: string): string =
  let sum = a.len + b.len
  result = newStringOfCap(sum)
  result.len = sum
  copyMem(addr result.data[0], a.data, a.len)
  copyMem(addr result.data[a.len], b.data, b.len)
  if sum > 0:
    result.data[sum] = '\0'

proc concat(x: openArray[string]): string {.core.} =
  ## used be the code generator to optimize 'x & y & z ...'
  var sum = 0
  for i in 0 ..< x.len: inc(sum, x[i].len)
  result = newStringOfCap(sum)
  sum = 0
  for i in 0 ..< x.len:
    let L = x[i].len
    copyMem(addr result.data[sum], x[i].data, L)
    inc(sum, L)

