changeset 1371:7427925a4246

add: library to parse toml files
author sam <sam@basx.dev>
date Wed, 04 Dec 2024 16:49:38 +0700
parents 18fda25d5d5f
children 2da623ec519b
files semicongine/thirdparty/attributions.txt semicongine/thirdparty/parsetoml.nim
diffstat 2 files changed, 1943 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/thirdparty/attributions.txt	Wed Dec 04 16:49:15 2024 +0700
+++ b/semicongine/thirdparty/attributions.txt	Wed Dec 04 16:49:38 2024 +0700
@@ -2,3 +2,4 @@
 win32 bindings (winim): Chen Kai-Hung, Ward (MIT)
 X11 bindings (x11): nim-lang (MIT)
 Compression library (zippy): Ryan Oldenburg (MIT)
+TOML parsing (parsetoml.nim): Maurizio Tomasi and contributors (MIT)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/thirdparty/parsetoml.nim	Wed Dec 04 16:49:38 2024 +0700
@@ -0,0 +1,1942 @@
+## :License: MIT
+##
+## Introduction
+## ============
+## This module implements a TOML parser that is compliant with v0.5.0 of its spec.
+##
+## Source
+## ======
+## `Repo link <https://github.com/NimParsers/parsetoml>`_
+##
+
+# Copyright (c) 2015 Maurizio Tomasi and contributors
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import math
+import streams
+import strutils
+import tables
+import unicode
+from parseutils import parseFloat
+
+export tables
+
+when (NimMajor, NimMinor, NimPatch) < (1, 4, 0):
+  type
+    IndexDefect* = IndexError
+    OverflowDefect* = OverflowError
+
+type
+  Sign* = enum None, Pos, Neg
+
+  TomlValueKind* {.pure.} = enum
+    None
+    Int,
+    Float,
+    Bool,
+    Datetime,
+    Date,
+    Time,
+    String,
+    Array,
+    Table
+
+  TomlDate* = object
+    year*: int
+    month*: int
+    day*: int
+
+  TomlTime* = object
+    hour*: int
+    minute*: int
+    second*: int
+    subsecond*: int
+
+  TomlDateTime* = object
+    date*: TomlDate
+    time*: TomlTime
+    case shift*: bool
+    of true:
+      isShiftPositive*: bool
+      zoneHourShift*: int
+      zoneMinuteShift*: int
+    of false: nil
+
+  TomlTable* = OrderedTable[string, TomlValueRef]
+  TomlTableRef* = ref TomlTable
+
+  TomlValueRef* = ref TomlValue
+  TomlValue* = object
+    case kind*: TomlValueKind
+    of TomlValueKind.None: nil
+    of TomlValueKind.Int: intVal*: int64
+    of TomlValueKind.Float:
+      floatVal*: float64
+      forcedSign*: Sign
+    of TomlValueKind.Bool: boolVal*: bool
+    of TomlValueKind.Datetime: dateTimeVal*: TomlDateTime
+    of TomlValueKind.Date: dateVal*: TomlDate
+    of TomlValueKind.Time: timeVal*: TomlTime
+    of TomlValueKind.String: stringVal*: string
+    of TomlValueKind.Array: arrayVal*: seq[TomlValueRef]
+    of TomlValueKind.Table: tableVal*: TomlTableRef
+
+  ParserState = object
+    fileName*: string
+    line*: int
+    column*: int
+    pushback: char
+    stream*: streams.Stream
+    curTableRef*: TomlTableRef
+
+  TomlError* = object of ValueError
+    location*: ParserState
+
+  NumberBase = enum
+    base10, base16, base8, base2
+
+  StringType {.pure.} = enum
+    Basic,  # Enclosed within double quotation marks
+    Literal # Enclosed within single quotation marks
+
+const
+  defaultStringCapacity = 256
+  ctrlChars = {'\x00' .. '\x08', '\x0A' .. '\x1F', '\x7F'} # '\x09' - TAB is not counted as control char
+  ctrlCharsExclCrLf = ctrlChars - {'\x0A', '\x0D'}
+
+proc newTomlError(location: ParserState, msg: string): ref TomlError =
+  result = newException(TomlError, location.fileName & "(" & $location.line &
+                        ":" & $location.column & ")" & " " & msg)
+  result.location = location
+
+proc getNextChar(state: var ParserState): char =
+  # Return the next available char from the stream associate with
+  # the parser state, or '\0' if there are no characters left.
+
+  if state.pushback != '\0':
+    # If we've just read a character without having interpreted
+    # it, just return it
+    result = state.pushback
+    state.pushback = '\0'
+  else:
+    if state.stream.atEnd():
+      return '\0'
+
+    result = state.stream.readChar()
+
+    # Update the line and column number
+    if result == '\l':
+      inc(state.line)
+      state.column = 1
+    elif result != '\r':
+      inc(state.column)
+
+proc pushBackChar(state: var ParserState, c: char) {.inline.} =
+  state.pushback = c
+
+type
+  LfSkipMode = enum
+    skipLf, skipNoLf
+
+proc getNextNonWhitespace(state: var ParserState,
+                          skip: LfSkipMode): char =
+  # Note: this procedure does *not* consider a newline as a
+  # "whitespace". Since newlines are often mandatory in TOML files
+  # (e.g. after a key/value specification), we do not want to miss
+  # them...
+
+  let whitespaces = (case skip
+                     of skipLf: {' ', '\t', '\r', '\l'}
+                     of skipNoLf: {' ', '\t', '\r'})
+
+  var nextChar: char
+  while true:
+    nextChar = state.getNextChar()
+    if nextChar == '#':
+      # Skip the comment up to the newline, but do not jump over it
+      while nextChar != '\l' and nextChar != '\0':
+        nextChar = state.getNextChar()
+        # https://toml.io/en/v1.0.0#comment
+        # Control characters other than tab (U+0009) are not permitted in comments.
+        # Invalid control characters: U+0000 to U+0008, U+000A to U+001F, U+007F
+        if nextChar in ctrlCharsExclCrLf:
+          raise newTomlError(state, "invalid control char 0x$# found in a comment" % [nextChar.ord.toHex(2)])
+
+    if nextChar notin whitespaces: break
+
+  result = nextChar
+
+proc charToInt(c: char, base: NumberBase): int {.inline, noSideEffect.} =
+  case base
+  of base10, base8, base2: result = int(c) - int('0')
+  of base16:
+    if c in strutils.Digits:
+      result = charToInt(c, base10)
+    else:
+      result = 10 + int(toUpperAscii(c)) - int('A')
+
+type
+  LeadingChar {.pure.} = enum
+    AllowZero, DenyZero
+
+proc parseInt(state: var ParserState,
+              base: NumberBase,
+              leadingChar: LeadingChar): int64 =
+  var
+    nextChar: char
+    firstPos = true
+    negative = false
+    wasUnderscore = false
+
+  let
+    baseNum = (case base
+               of base2: 2
+               of base8: 8
+               of base10: 10
+               of base16: 16)
+    digits = (case base
+              of base2: {'0', '1'}
+              of base8: {'0', '1', '2', '3', '4', '5', '6', '7'}
+              of base10: strutils.Digits
+              of base16: strutils.HexDigits)
+
+  result = 0
+  while true:
+    wasUnderscore = nextChar == '_'
+    nextChar = state.getNextChar()
+    if nextChar == '_':
+      if firstPos or wasUnderscore:
+        raise(newTomlError(state,
+                           "underscore must be surrounded by digit"))
+      continue
+
+    if nextChar in {'+', '-'} and firstPos:
+      firstPos = false
+      if nextChar == '-': negative = true
+      continue
+
+    if nextChar == '0' and firstPos and leadingChar == LeadingChar.DenyZero:
+      # TOML specifications forbid this
+      var upcomingChar = state.getNextChar()
+      if upcomingChar in Digits:
+        raise(newTomlError(state,
+                           "leading zeroes are not allowed in integers"))
+      else:
+        state.pushBackChar(upcomingChar)
+
+    if nextChar notin digits:
+      if wasUnderscore:
+        raise(newTomlError(state,
+                           "underscore must be surrounded by digit"))
+      state.pushBackChar(nextChar)
+      break
+
+    try:
+      result = result * baseNum - charToInt(nextChar, base)
+    except OverflowDefect:
+      raise(newTomlError(state,
+                         "integer numbers wider than 64 bits not allowed"))
+
+    firstPos = false
+
+  if not negative:
+    try:
+      result = -result
+    except OverflowDefect:
+      raise(newTomlError(state,
+                         "integer numbers wider than 64 bits not allowed"))
+
+proc parseEncoding(state: var ParserState): TomlValueRef =
+  let nextChar = state.getNextChar()
+  case nextChar:
+    of 'b':
+      return TomlValueRef(kind: TomlValueKind.Int, intVal: parseInt(state, base2, LeadingChar.AllowZero))
+    of 'o':
+      return TomlValueRef(kind: TomlValueKind.Int, intVal: parseInt(state, base8, LeadingChar.AllowZero))
+    of 'x':
+      return TomlValueRef(kind: TomlValueKind.Int, intVal: parseInt(state, base16, LeadingChar.AllowZero))
+    else: raise newTomlError(state, "illegal character")
+
+proc parseDecimalPart(state: var ParserState): float64 =
+  var
+    nextChar: char
+    firstPos = true
+    wasUnderscore = false
+    decimalPartStr = "0."
+
+  while true:
+    wasUnderscore = nextChar == '_'
+    nextChar = state.getNextChar()
+    if nextChar == '_':
+      if firstPos or wasUnderscore:
+        raise(newTomlError(state,
+                           "underscore must be surrounded by digit"))
+      continue
+    if nextChar notin strutils.Digits:
+      if wasUnderscore:
+        raise(newTomlError(state,
+                           "underscore must be surrounded by digit"))
+      state.pushBackChar(nextChar)
+      if firstPos:
+        raise newTomlError(state, "decimal part empty")
+      break
+
+    decimalPartStr.add(nextChar)
+
+    firstPos = false
+  doAssert decimalPartStr.len > 2 # decimalPartStr shouldn't still be "0." at this point
+  discard parseutils.parseFloat(decimalPartStr, result)
+
+proc stringDelimiter(kind: StringType): char {.inline, noSideEffect.} =
+  result = (case kind
+            of StringType.Basic: '\"'
+            of StringType.Literal: '\'')
+
+proc parseUnicode(state: var ParserState): string =
+  let
+    escapeKindChar = state.getNextChar()
+    oldState = (column: state.column, line: state.line)
+    code = parseInt(state, base16, LeadingChar.AllowZero)
+  if state.line != oldState.line:
+    raise newTomlError(state, "invalid Unicode codepoint, can't span lines")
+  if escapeKindChar == 'u' and state.column - 5 != oldState.column:
+    raise newTomlError(state, "invalid Unicode codepoint, 'u' must have " &
+                       "four character value")
+  if escapeKindChar == 'U' and state.column - 9 != oldState.column:
+    raise newTomlError(state, "invalid Unicode codepoint, 'U' must have " &
+                       "eight character value")
+  if code notin 0'i64..0xD7FF and code notin 0xE000'i64..0x10FFFF:
+    raise(newTomlError(state, "invalid Unicode codepoint, " &
+                       "must be a Unicode scalar value"))
+
+  return unicode.toUTF8(Rune(code))
+
+proc parseEscapeChar(state: var ParserState, escape: char): string =
+  case escape
+  of 'b': result = "\b"
+  of 't': result = "\t"
+  of 'n': result = "\l"
+  of 'f': result = "\f"
+  of 'r': result = "\r"
+  of '\'': result = "\'"
+  of '\"': result = "\""
+  of '\\': result = "\\"
+  of 'u', 'U':
+    state.pushBackChar(escape)
+    result = parseUnicode(state)
+  else:
+    raise(newTomlError(state,
+                       "unknown escape " &
+                       "sequence \"\\" & escape & "\""))
+
+proc parseSingleLineString(state: var ParserState, kind: StringType): string =
+  # This procedure parses strings enclosed within single/double
+  # quotation marks. It assumes that the quotation mark has already
+  # been consumed by the "state" variable, which therefore is ready
+  # to read the first character of the string.
+
+  result = newStringOfCap(defaultStringCapacity)
+
+  let delimiter = stringDelimiter(kind)
+
+  var nextChar: char
+  while true:
+    nextChar = state.getNextChar()
+    if nextChar == delimiter:
+      break
+
+    if nextChar == '\0':
+      raise(newTomlError(state, "unterminated string"))
+
+    # https://toml.io/en/v1.0.0#string
+    # Any Unicode character may be used except those that must be escaped:
+    # quotation mark, backslash, and the control characters other than tab
+    # (U+0000 to U+0008, U+000A to U+001F, U+007F).
+    if nextChar in ctrlChars:
+      raise(newTomlError(state, "invalid character in string: 0x$#" % nextChar.ord.toHex(2)))
+
+    if nextChar == '\\' and kind == StringType.Basic:
+      nextChar = state.getNextChar()
+      result.add(state.parseEscapeChar(nextChar))
+      continue
+
+    result.add(nextChar)
+
+proc parseMultiLineString(state: var ParserState, kind: StringType): string =
+  # This procedure parses strings enclosed within three consecutive
+  # sigle/double quotation marks. It assumes that all the quotation
+  # marks have already been consumed by the "state" variable, which
+  # therefore is ready to read the first character of the string.
+
+  result = newStringOfCap(defaultStringCapacity)
+  let delimiter = stringDelimiter(kind)
+  var
+    isFirstChar = true
+    nextChar: char
+  while true:
+    nextChar = state.getNextChar()
+
+    # Skip the first newline, if it comes immediately after the
+    # quotation marks
+    if isFirstChar and (nextChar == '\l'):
+      isFirstChar = false
+      continue
+
+    if nextChar == delimiter:
+      # Are we done?
+      nextChar = state.getNextChar()
+      if nextChar == delimiter:
+        nextChar = state.getNextChar()
+        if nextChar == delimiter:
+          # Done with this string
+          return
+        else:
+          # Just got a double delimiter
+          result.add(delimiter & delimiter)
+          state.pushBackChar(nextChar)
+          continue
+      else:
+        # Just got a lone delimiter
+        result.add(delimiter)
+        state.pushBackChar(nextChar)
+        continue
+
+    if nextChar == '\\' and kind == StringType.Basic:
+      # This can either be an escape sequence or a end-of-line char
+      nextChar = state.getNextChar()
+      if nextChar in {'\l', '\r', ' '}:
+        # We're at the end of a line: skip everything till the
+        # next non-whitespace character
+        while nextChar in {'\l', '\r', ' ', '\t'}:
+          nextChar = state.getNextChar()
+
+        state.pushBackChar(nextChar)
+        continue
+      else:
+        # This is just an escape sequence (like "\t")
+        #nextChar = state.getNextChar()
+        result.add(state.parseEscapeChar(nextChar))
+        continue
+
+    if nextChar == '\0':
+      raise(newTomlError(state, "unterminated string"))
+
+    # https://toml.io/en/v1.0.0#string
+    # Any Unicode character may be used except those that must be
+    # escaped: backslash and the control characters other than tab,
+    # line feed, and carriage return (U+0000 to U+0008, U+000B,
+    # U+000C, U+000E to U+001F, U+007F).
+    if nextChar in ctrlCharsExclCrLf:
+      raise(newTomlError(state, "invalid character in string: 0x$#" % nextChar.ord.toHex(2)))
+
+    result.add(nextChar)
+    isFirstChar = false
+
+proc parseString(state: var ParserState, kind: StringType): string =
+  ## This function assumes that "state" has already consumed the
+  ## first character (either \" or \', which is passed in the
+  ## "openChar" parameter).
+
+  let delimiter = stringDelimiter(kind)
+  var nextChar: char = state.getNextChar()
+  if nextChar == delimiter:
+    # We have two possibilities here: (1) the empty string, or (2)
+    # "long" multi-line strings.
+    nextChar = state.getNextChar()
+    if nextChar == delimiter:
+      return parseMultiLineString(state, kind)
+    else:
+      # Empty string. This was easy!
+      state.pushBackChar(nextChar)
+      return ""
+  else:
+    state.pushBackChar(nextChar)
+    return parseSingleLineString(state, kind)
+
+# Forward declaration
+proc parseValue(state: var ParserState): TomlValueRef
+proc parseInlineTable(state: var ParserState): TomlValueRef
+
+proc parseArray(state: var ParserState): seq[TomlValueRef] =
+  # This procedure assumes that "state" has already consumed the '['
+  # character
+
+  result = newSeq[TomlValueRef](0)
+
+  while true:
+    var nextChar: char = state.getNextNonWhitespace(skipLf)
+    case nextChar
+    of ']':
+      return
+    of ',':
+      if len(result) == 0:
+        # This happens with "[, 1, 2]", for instance
+        raise(newTomlError(state, "first array element missing"))
+
+      # Check that this is not a terminating comma (like in
+      #  "[b,]")
+      nextChar = state.getNextNonWhitespace(skipLf)
+      if nextChar == ']':
+        return
+
+      state.pushBackChar(nextChar)
+    else:
+      let oldState = state # Saved for error messages
+      var newValue: TomlValueRef
+      if nextChar != '{':
+        state.pushBackChar(nextChar)
+        newValue = parseValue(state)
+      else:
+        newValue = parseInlineTable(state)
+
+      if len(result) > 0:
+        # Check that the type of newValue is compatible with the
+        # previous ones
+        if newValue.kind != result[low(result)].kind:
+          raise(newTomlError(oldState,
+                             "array members with incompatible types"))
+
+      result.add(newValue)
+
+proc parseStrictNum(state: var ParserState,
+                    minVal: int,
+                    maxVal: int,
+                    count: Slice[int],
+                    msg: string): int =
+  var
+    nextChar: char
+    parsedChars = 0
+
+  result = 0
+  while true:
+    nextChar = state.getNextChar()
+
+    if nextChar notin strutils.Digits:
+      state.pushBackChar(nextChar)
+      break
+
+    try:
+      result = result * 10 + charToInt(nextChar, base10)
+      parsedChars += 1
+    except OverflowDefect:
+      raise(newTomlError(state,
+                         "integer numbers wider than 64 bits not allowed"))
+
+  if parsedChars notin count:
+    raise(newTomlError(state,
+                       "too few or too many characters in digit, expected " &
+                       $count & " got " & $parsedChars))
+
+  if result < minVal or result > maxVal:
+    raise(newTomlError(state, msg & " (" & $result & ")"))
+
+template parseStrictNum(state: var ParserState,
+                    minVal: int,
+                    maxVal: int,
+                    count: int,
+                    msg: string): int =
+  parseStrictNum(state, minVal, maxVal, (count..count), msg)
+
+proc parseTimePart(state: var ParserState, val: var TomlTime) =
+  var
+    nextChar: char
+    curLine = state.line
+
+  # Parse the minutes
+  val.minute = parseStrictNum(state, minVal = 0, maxVal = 59, count = 2,
+                                   "number out of range for minutes")
+  if curLine != state.line:
+    raise(newTomlError(state, "invalid date field, stopped in or after minutes field"))
+
+  nextChar = state.getNextChar()
+  if nextChar != ':':
+    raise(newTomlError(state,
+                       "\":\" expected after the number of seconds"))
+
+  # Parse the second. Note that seconds=60 *can* happen (leap second)
+  val.second = parseStrictNum(state, minVal = 0, maxVal = 60, count = 2,
+                                   "number out of range for seconds")
+
+  nextChar = state.getNextChar()
+  if nextChar == '.':
+    val.subsecond = parseInt(state, base10, LeadingChar.AllowZero).int
+  else:
+    state.pushBackChar(nextChar)
+
+proc parseDateTimePart(state: var ParserState,
+                       dateTime: var TomlDateTime): bool =
+
+  # This function is called whenever a datetime object is found. They follow
+  # an ISO convention and can use one of the following format:
+  #
+  # - YYYY-MM-DDThh:mm:ss[+-]hh:mm
+  # - YYYY-MM-DDThh:mm:ssZ
+  #
+  # where the "T" and "Z" letters are literals, [+-] indicates
+  # *either* "+" or "-", YYYY is the 4-digit year, MM is the 2-digit
+  # month, DD is the 2-digit day, hh is the 2-digit hour, mm is the
+  # 2-digit minute, and ss is the 2-digit second. The hh:mm after
+  # the +/- character is the timezone; a literal "Z" indicates the
+  # local timezone.
+
+  # This function assumes that the "YYYY-" part has already been
+  # parsed (this happens because during parsing, finding a 4-digit
+  # number like "YYYY" might just indicate the presence of an
+  # integer or a floating-point number; it's the following "-" that
+  # tells the parser that the value is a datetime). As a consequence
+  # of this, we assume that "dateTime.year" has already been set.
+
+  var
+    nextChar: char
+    curLine = state.line
+
+  # Parse the month
+  dateTime.date.month = parseStrictNum(state, minVal = 1, maxVal = 12, count = 2,
+                                  "number out of range for the month")
+  if curLine != state.line:
+    raise(newTomlError(state, "invalid date field, stopped in or after month field"))
+
+  nextChar = state.getNextChar()
+  if nextChar != '-':
+    raise(newTomlError(state, "\"-\" expected after the month number"))
+
+  # Parse the day
+  dateTime.date.day = parseStrictNum(state, minVal = 1, maxVal = 31, count = 2,
+                                "number out of range for the day")
+  if curLine != state.line:
+    return false
+  else:
+    nextChar = state.getNextChar()
+    if nextChar notin {'t', 'T', ' '}:
+      raise(newTomlError(state, "\"T\", \"t\", or space expected after the day number"))
+
+    # Parse the hour
+    dateTime.time.hour = parseStrictNum(state, minVal = 0, maxVal = 23, count = 2,
+                                   "number out of range for the hours")
+    if curLine != state.line:
+      raise(newTomlError(state, "invalid date field, stopped in or after hours field"))
+
+    nextChar = state.getNextChar()
+    if nextChar != ':':
+      raise(newTomlError(state, "\":\" expected after the number of hours"))
+
+    # Parse the minutes
+    dateTime.time.minute = parseStrictNum(state, minVal = 0, maxVal = 59, count = 2,
+                                     "number out of range for minutes")
+    if curLine != state.line:
+      raise(newTomlError(state, "invalid date field, stopped in or after minutes field"))
+
+    nextChar = state.getNextChar()
+    if nextChar != ':':
+      raise(newTomlError(state,
+                         "\":\" expected after the number of seconds"))
+
+    # Parse the second. Note that seconds=60 *can* happen (leap second)
+    dateTime.time.second = parseStrictNum(state, minVal = 0, maxVal = 60, count = 2,
+                                     "number out of range for seconds")
+
+    nextChar = state.getNextChar()
+    if nextChar == '.':
+      dateTime.time.subsecond = parseInt(state, base10, LeadingChar.AllowZero).int
+    else:
+      state.pushBackChar(nextChar)
+
+    nextChar = state.getNextChar()
+    case nextChar
+    of 'z', 'Z':
+      dateTime = TomlDateTime(
+        time: dateTime.time,
+        date: dateTime.date,
+        shift: true,
+        isShiftPositive: true,
+        zoneHourShift: 0,
+        zoneMinuteShift: 0
+      )
+    of '+', '-':
+      dateTime = TomlDateTime(
+        time: dateTime.time,
+        date: dateTime.date,
+        shift: true,
+        isShiftPositive: (nextChar == '+')
+      )
+      dateTime.zoneHourShift =
+        parseStrictNum(state, minVal = 0, maxVal = 23, count = 2,
+                               "number out of range for shift hours")
+      if curLine != state.line:
+        raise(newTomlError(state, "invalid date field, stopped in or after shift hours field"))
+
+      nextChar = state.getNextChar()
+      if nextChar != ':':
+        raise(newTomlError(state,
+                           "\":\" expected after the number of shift hours"))
+
+      dateTime.zoneMinuteShift =
+        parseStrictNum(state, minVal = 0, maxVal = 59, count = 2,
+                               "number out of range for shift minutes")
+    else:
+      if curLine == state.line:
+        raise(newTomlError(state, "unexpected character " & escape($nextChar) &
+                           " instead of the time zone"))
+      else: # shift is automatically initialized to false
+        state.pushBackChar(nextChar)
+
+    return true
+
+proc parseDateOrTime(state: var ParserState, digits: int, yearOrHour: int): TomlValueRef =
+  var
+    nextChar: char
+    yoh = yearOrHour
+    d = digits
+  while true:
+    nextChar = state.getNextChar()
+    case nextChar:
+      of ':':
+        if d != 2:
+          raise newTomlError(state, "wrong number of characters for hour")
+        var val: TomlTime
+        val.hour = yoh
+
+        parseTimePart(state, val)
+        return TomlValueRef(kind: TomlValueKind.Time, timeVal: val)
+      of '-':
+        if d != 4:
+          raise newTomlError(state, "wrong number of characters for year")
+        var val: TomlDateTime
+        val.date.year = yoh
+        let fullDate = parseDateTimePart(state, val)
+
+        if fullDate:
+          return TomlValueRef(kind: TomlValueKind.DateTime,
+                                dateTimeVal: val)
+        else:
+          return TomlValueRef(kind: TomlValueKind.Date,
+                                dateVal: val.date)
+      of strutils.Digits:
+        if d == 4:
+          raise newTomlError(state, "leading zero not allowed")
+        try:
+          yoh *= 10
+          yoh += ord(nextChar) - ord('0')
+          d += 1
+        except OverflowDefect:
+          raise newTomlError(state, "number larger than 64 bits wide")
+        continue
+      of strutils.Whitespace:
+        raise newTomlError(state, "leading zero not allowed")
+      else: raise newTomlError(state, "illegal character")
+    break
+
+proc parseFloat(state: var ParserState, intPart: int, forcedSign: Sign): TomlValueRef =
+  var
+    decimalPart = parseDecimalPart(state)
+    nextChar = state.getNextChar()
+    exponent: int64 = 0
+  if nextChar in {'e', 'E'}:
+    exponent = parseInt(state, base10, LeadingChar.AllowZero)
+  else:
+    state.pushBackChar(nextChar)
+
+  let value =
+    if intPart <= 0:
+      pow(10.0, exponent.float64) * (float64(intPart) - decimalPart)
+    else:
+      pow(10.0, exponent.float64) * (float64(intPart) + decimalPart)
+  return TomlValueRef(kind: TomlValueKind.Float, floatVal: if forcedSign != Neg: -value else: value, forcedSign: forcedSign)
+
+proc parseNumOrDate(state: var ParserState): TomlValueRef =
+  var
+    nextChar: char
+    forcedSign: Sign = None
+
+  while true:
+    nextChar = state.getNextChar()
+    case nextChar:
+      of '0':
+        nextChar = state.getNextChar()
+        if forcedSign == None:
+          if nextChar in {'b', 'x', 'o'}:
+            state.pushBackChar(nextChar)
+            return parseEncoding(state)
+          else:
+            # This must now be a float or a date/time, or a sole 0
+            case nextChar:
+              of '.':
+                return parseFloat(state, 0, forcedSign)
+              of strutils.Whitespace:
+                state.pushBackChar(nextChar)
+                return TomlValueRef(kind: TomlValueKind.Int, intVal: 0)
+              of strutils.Digits:
+                # This must now be a date/time
+                return parseDateOrTime(state, digits = 2, yearOrHour = ord(nextChar) - ord('0'))
+              else:
+                # else is a sole 0
+                return TomlValueRef(kind: TomlValueKind.Int, intVal: 0)
+        else:
+          # This must now be a float, or a sole 0
+          case nextChar:
+            of '.':
+              return parseFloat(state, 0, forcedSign)
+            of strutils.Whitespace:
+              state.pushBackChar(nextChar)
+              return TomlValueRef(kind: TomlValueKind.Int, intVal: 0)
+            else:
+              # else is a sole 0
+              return TomlValueRef(kind: TomlValueKind.Int, intVal: 0)
+      of strutils.Digits - {'0'}:
+        # This might be a date/time, or an int or a float
+        var
+          digits = 1
+          curSum = ord('0') - ord(nextChar)
+          wasUnderscore = false
+        while true:
+          nextChar = state.getNextChar()
+          if wasUnderscore and nextChar notin strutils.Digits:
+            raise newTomlError(state, "underscores must be surrounded by digits")
+          case nextChar:
+            of ':':
+              if digits != 2:
+                raise newTomlError(state, "wrong number of characters for hour")
+              var val: TomlTime
+              val.hour = -curSum
+              parseTimePart(state, val)
+              return TomlValueRef(kind: TomlValueKind.Time, timeVal: val)
+            of '-':
+              if digits != 4:
+                raise newTomlError(state, "wrong number of characters for year")
+              var val: TomlDateTime
+              val.date.year = -curSum
+              let fullDate = parseDateTimePart(state, val)
+              if fullDate:
+                return TomlValueRef(kind: TomlValueKind.DateTime,
+                                      dateTimeVal: val)
+              else:
+                return TomlValueRef(kind: TomlValueKind.Date,
+                                      dateVal: val.date)
+            of '.':
+              return parseFloat(state, curSum, forcedSign)
+            of 'e', 'E':
+              var exponent = parseInt(state, base10, LeadingChar.AllowZero)
+              let value = pow(10.0, exponent.float64) * float64(curSum)
+              return TomlValueRef(kind: TomlValueKind.Float, floatVal: if forcedSign != Neg: -value else: value)
+            of strutils.Digits:
+              try:
+                curSum *= 10
+                curSum += ord('0') - ord(nextChar)
+                digits += 1
+              except OverflowDefect:
+                raise newTomlError(state, "number larger than 64 bits wide")
+              wasUnderscore = false
+              continue
+            of '_':
+              wasUnderscore = true
+              continue
+            of strutils.Whitespace:
+              state.pushBackChar(nextChar)
+              return TomlValueRef(kind: TomlValueKind.Int, intVal: if forcedSign != Neg: -curSum else: curSum)
+            else:
+              state.pushBackChar(nextChar)
+              return TomlValueRef(kind: TomlValueKind.Int, intVal: if forcedSign != Neg: -curSum else: curSum)
+          break
+      of '+', '-':
+        forcedSign = if nextChar == '+': Pos else: Neg
+        continue
+      of 'i':
+        #  Is this "inf"?
+        let oldState = state
+        if state.getNextChar() != 'n' or
+           state.getNextChar() != 'f':
+            raise(newTomlError(oldState, "unknown identifier"))
+        return TomlValueRef(kind: TomlValueKind.Float, floatVal: if forcedSign == Neg: NegInf else: Inf, forcedSign: forcedSign)
+
+      of 'n':
+        #  Is this "nan"?
+        let oldState = state
+        if state.getNextChar() != 'a' or
+           state.getNextChar() != 'n':
+            raise(newTomlError(oldState, "unknown identifier"))
+        return TomlValueRef(kind: TomlValueKind.Float, floatVal: NaN, forcedSign: forcedSign)
+      else:
+        raise newTomlError(state, "illegal character " & escape($nextChar))
+    break
+
+proc parseValue(state: var ParserState): TomlValueRef =
+  var nextChar: char
+
+  nextChar = state.getNextNonWhitespace(skipNoLf)
+  case nextChar
+  of strutils.Digits, '+', '-', 'i', 'n':
+    state.pushBackChar(nextChar)
+    return parseNumOrDate(state)
+  of 't':
+    # Is this "true"?
+    let oldState = state # Only used for error messages
+    if state.getNextChar() != 'r' or
+       state.getNextChar() != 'u' or
+       state.getNextChar() != 'e':
+        raise(newTomlError(oldState, "unknown identifier"))
+    result = TomlValueRef(kind: TomlValueKind.Bool, boolVal: true)
+
+  of 'f':
+    # Is this "false"?
+    let oldState = state # Only used for error messages
+    if state.getNextChar() != 'a' or
+       state.getNextChar() != 'l' or
+       state.getNextChar() != 's' or
+       state.getNextChar() != 'e':
+        raise(newTomlError(oldState, "unknown identifier"))
+    result = TomlValueRef(kind: TomlValueKind.Bool, boolVal: false)
+
+  of '\"':
+    # A basic string (accepts \ escape codes)
+    result = TomlValueRef(kind: TomlValueKind.String,
+                          stringVal: parseString(state, StringType.Basic))
+
+  of '\'':
+    # A literal string (does not accept \ escape codes)
+    result = TomlValueRef(kind: TomlValueKind.String,
+                          stringVal: parseString(state, StringType.Literal))
+
+  of '[':
+    # An array
+    result = TomlValueRef(kind: TomlValueKind.Array,
+                          arrayVal: parseArray(state))
+  else:
+    raise(newTomlError(state,
+                       "unexpected character " & escape($nextChar)))
+
+proc parseName(state: var ParserState): string =
+  # This parses the name of a key or a table
+  result = newStringOfCap(defaultStringCapacity)
+
+  var nextChar = state.getNextNonWhitespace(skipNoLf)
+  if nextChar == '\"':
+    return state.parseString(StringType.Basic)
+  elif nextChar == '\'':
+    return state.parseString(StringType.Literal)
+  state.pushBackChar(nextChar)
+  while true:
+    nextChar = state.getNextChar()
+    if (nextChar in {'=', '.', '[', ']', '\0', ' ', '\t'}):
+      # Any of the above characters marks the end of the name
+      state.pushBackChar(nextChar)
+      break
+    elif (nextChar notin {'a'..'z', 'A'..'Z', '0'..'9', '_', '-'}):
+      raise(newTomlError(state,
+        "bare key has illegal character: " & escape($nextChar)))
+    else:
+      result.add(nextChar)
+
+type
+  BracketType {.pure.} = enum
+    single, double
+
+proc parseTableName(state: var ParserState,
+                    brackets: BracketType): seq[string] =
+  # This code assumes that '[' has already been consumed
+  result = newSeq[string](0)
+
+  while true:
+    #let partName = state.parseName(SpecialChars.AllowNumberSign)
+    var
+      nextChar = state.getNextChar()
+      partName: string
+    if nextChar == '"':
+      partName = state.parseString(StringType.Basic)
+    else:
+      state.pushBackChar(nextChar)
+      partName = state.parseName()
+    result.add(partName)
+
+    nextChar = state.getNextNonWhitespace(skipNoLf)
+    case nextChar
+    of ']':
+      if brackets == BracketType.double:
+        nextChar = state.getNextChar()
+        if nextChar != ']':
+          raise(newTomlError(state,
+                             "\"]]\" expected"))
+
+      # We must check that there is nothing else in this line
+      nextChar = state.getNextNonWhitespace(skipNoLf)
+      if nextChar notin {'\l', '\0'}:
+        raise(newTomlError(state,
+                           "unexpected character " & escape($nextChar)))
+
+      break
+
+    of '.': continue
+    else:
+      raise(newTomlError(state,
+                         "unexpected character " & escape($nextChar)))
+
+proc setEmptyTableVal(val: var TomlValueRef) =
+  val = TomlValueRef(kind: TomlValueKind.Table)
+  new(val.tableVal)
+  val.tableVal[] = initOrderedTable[string, TomlValueRef]()
+
+proc parseInlineTable(state: var ParserState): TomlValueRef =
+  new(result)
+  setEmptyTableVal(result)
+  var firstComma = true
+  while true:
+    var nextChar = state.getNextNonWhitespace(skipNoLf)
+    case nextChar
+    of '}':
+      return
+    of ',':
+      if firstComma:
+        raise(newTomlError(state, "first inline table element missing"))
+      # Check that this is not a terminating comma (like in
+      #  "[b,]")
+      nextChar = state.getNextNonWhitespace(skipNoLf)
+      if nextChar == '}':
+        return
+
+      state.pushBackChar(nextChar)
+    of '\n':
+      raise(newTomlError(state, "inline tables cannot contain newlines"))
+    else:
+      firstComma = false
+      state.pushBackChar(nextChar)
+      var key = state.parseName()
+
+      nextChar = state.getNextNonWhitespace(skipNoLf)
+      var curTable = result.tableVal
+      while nextChar == '.':
+        var deepestTable = new(TomlTableRef)
+        deepestTable[] = initOrderedTable[string, TomlValueRef]()
+        curTable[key] = TomlValueRef(kind: TomlValueKind.Table, tableVal: deepestTable)
+        curTable = deepestTable
+        key = state.parseName()
+        nextChar = state.getNextNonWhitespace(skipNoLf)
+
+      if nextChar != '=':
+        raise(newTomlError(state,
+                           "key names cannot contain spaces"))
+      nextChar = state.getNextNonWhitespace(skipNoLf)
+      if nextChar == '{':
+        curTable[key] = state.parseInlineTable()
+      else:
+        state.pushBackChar(nextChar)
+        curTable[key] = state.parseValue()
+
+proc createTableDef(state: var ParserState,
+                    tableNames: seq[string],
+                    dotted = false)
+
+proc parseKeyValuePair(state: var ParserState) =
+  var
+    tableKeys: seq[string]
+    key: string
+    nextChar: char
+    oldTableRef = state.curTableRef
+
+  while true:
+    let subkey = state.parseName()
+
+    nextChar = state.getNextNonWhitespace(skipNoLf)
+    if nextChar == '.':
+      tableKeys.add subkey
+    else:
+      if tableKeys.len != 0:
+        createTableDef(state, tableKeys, dotted = true)
+      key = subkey
+      break
+
+  if nextChar != '=':
+    raise(newTomlError(state,
+                       "key names cannot contain character \"" & nextChar & "\""))
+
+  nextChar = state.getNextNonWhitespace(skipNoLf)
+  # Check that this is a regular value and not an inline table
+  if nextChar != '{':
+    state.pushBackChar(nextChar)
+    let value = state.parseValue()
+
+    # We must check that there is nothing else in this line
+    nextChar = state.getNextNonWhitespace(skipNoLf)
+    if nextChar != '\l' and nextChar != '\0':
+      raise(newTomlError(state,
+                         "unexpected character " & escape($nextChar)))
+
+    if state.curTableRef.hasKey(key):
+      raise(newTomlError(state,
+                         "duplicate key, \"" & key & "\" already in table"))
+    state.curTableRef[key] = value
+  else:
+    #createTableDef(state, @[key])
+    if key.len == 0:
+      raise newTomlError(state, "empty key not allowed")
+    if state.curTableRef.hasKey(key):
+      raise newTomlError(state, "duplicate table key not allowed")
+    state.curTableRef[key] = parseInlineTable(state)
+
+  state.curTableRef = oldTableRef
+
+proc newParserState(s: streams.Stream,
+                    fileName: string = ""): ParserState =
+  result = ParserState(fileName: fileName, line: 1, column: 1, stream: s)
+
+proc setArrayVal(val: var TomlValueRef, numOfElems: int = 0) =
+  val = TomlValueRef(kind: TomlValueKind.Array)
+  val.arrayVal = newSeq[TomlValueRef](numOfElems)
+
+proc advanceToNextNestLevel(state: var ParserState,
+                            tableName: string) =
+  let target = state.curTableRef[tableName]
+  case target.kind
+  of TomlValueKind.Table:
+    state.curTableRef = target.tableVal
+  of TomlValueKind.Array:
+    let arr = target.arrayVal[high(target.arrayVal)]
+    if arr.kind != TomlValueKind.Table:
+      raise(newTomlError(state, "\"" & tableName &
+                         "\" elements are not tables"))
+    state.curTableRef = arr.tableVal
+  else:
+    raise(newTomlError(state, "\"" & tableName &
+                       "\" is not a table"))
+
+# This function is called by the TOML parser whenever a
+# "[[table.name]]" line is encountered in the parsing process. Its
+# purpose is to make sure that all the parent nodes in "table.name"
+# exist and are tables, and that a terminal node of the correct type
+# is created.
+#
+# Starting from "curTableRef" (which is usually the root object),
+# traverse the object tree following the names in "tableNames" and
+# create a new TomlValueRef object of kind "TomlValueKind.Array" at
+# the terminal node. This array is going to be an array of tables: the
+# function will create an element and will make "curTableRef"
+# reference it. Example: if tableNames == ["a", "b", "c"], the code
+# will look for the "b" table that is child of "a", and then it will
+# check if "c" is a child of "b". If it is, it must be an array of
+# tables, and a new element will be appended. Otherwise, a new "c"
+# array is created, and an empty table element is added in "c". In
+# either cases, curTableRef will refer to the last element of "c".
+
+proc createOrAppendTableArrayDef(state: var ParserState,
+                                 tableNames: seq[string]) =
+  # This is a table array entry (e.g. "[[entry]]")
+  for idx, tableName in tableNames:
+    if tableName.len == 0:
+      raise(newTomlError(state,
+                         "empty key not allowed"))
+    let lastTableInChain = idx == high(tableNames)
+
+    var newValue: TomlValueRef
+    if not state.curTableRef.hasKey(tableName):
+      # If this element does not exist, create it
+      new(newValue)
+
+      # If this is the last name in the chain (e.g.,
+      # "c" in "a.b.c"), its value should be an
+      # array of tables, otherwise just a table
+      if lastTableInChain:
+        setArrayVal(newValue, 1)
+
+        new(newValue.arrayVal[0])
+        setEmptyTableVal(newValue.arrayVal[0])
+
+        state.curTableRef[tableName] = newValue
+        state.curTableRef = newValue.arrayVal[0].tableVal
+      else:
+        setEmptyTableVal(newValue)
+
+        # Add the newly created object to the current table
+        state.curTableRef[tableName] = newValue
+
+        # Update the pointer to the current table
+        state.curTableRef = newValue.tableVal
+    else:
+      # The element exists: is it of the right type?
+      let target = state.curTableRef[tableName]
+
+      if lastTableInChain:
+        if target.kind != TomlValueKind.Array:
+          raise(newTomlError(state, "\"" & tableName &
+                                    "\" is not an array"))
+
+        var newValue: TomlValueRef
+        new(newValue)
+        setEmptyTableVal(newValue)
+        target.arrayVal.add(newValue)
+        state.curTableRef = newValue.tableVal
+      else:
+        advanceToNextNestLevel(state, tableName)
+
+# Starting from "curTableRef" (which is usually the root object),
+# traverse the object tree following the names in "tableNames" and
+# create a new TomlValueRef object of kind "TomlValueKind.Table" at
+# the terminal node. Example: if tableNames == ["a", "b", "c"], the
+# code will look for the "b" table that is child of "a" and it will
+# create a new table "c" which is "b"'s children.
+
+proc createTableDef(state: var ParserState,
+                    tableNames: seq[string],
+                    dotted = false) =
+  var newValue: TomlValueRef
+
+  # This starts a new table (e.g. "[table]")
+  for i, tableName in tableNames:
+    if tableName.len == 0:
+      raise(newTomlError(state,
+                         "empty key not allowed"))
+    if not state.curTableRef.hasKey(tableName):
+      new(newValue)
+      setEmptyTableVal(newValue)
+
+      # Add the newly created object to the current table
+      state.curTableRef[tableName] = newValue
+
+      # Update the pointer to the current table
+      state.curTableRef = newValue.tableVal
+    else:
+      if i == tableNames.high and state.curTableRef.hasKey(tableName) and
+        state.curTableRef[tableName].kind == TomlValueKind.Table:
+        if state.curTableRef[tableName].tableVal.len == 0:
+          raise newTomlError(state, "duplicate table key not allowed")
+        elif not dotted:
+          for value in state.curTableRef[tableName].tableVal.values:
+            if value.kind != TomlValueKind.Table:
+              raise newTomlError(state, "duplicate table key not allowed")
+      advanceToNextNestLevel(state, tableName)
+
+proc parseStream*(inputStream: streams.Stream,
+                  fileName: string = ""): TomlValueRef =
+  ## Parses a stream of TOML formatted data into a TOML table. The optional
+  ## filename is used for error messages.
+  if inputStream == nil:
+    raise newException(IOError,
+      "Unable to read from the stream created from: \"" & fileName & "\", " &
+      "possibly a missing file")
+  var state = newParserState(inputStream, fileName)
+  result = TomlValueRef(kind: TomlValueKind.Table)
+  new(result.tableVal)
+  result.tableVal[] = initOrderedTable[string, TomlValueRef]()
+
+  # This pointer will always point to the table that should get new
+  # key/value pairs found in the TOML file during parsing
+  state.curTableRef = result.tableVal
+
+  # Unlike "curTableRef", this pointer never changes: it always
+  # points to the uppermost table in the tree
+  let baseTable = result.tableVal
+
+  var nextChar: char
+  while true:
+    nextChar = state.getNextNonWhitespace(skipLf)
+    case nextChar
+    of '[':
+      # A new section/table begins. We'll have to start again
+      # from the uppermost level, so let's rewind curTableRef to
+      # the root node
+      state.curTableRef = baseTable
+
+      # First, decompose the table name into its part (e.g.,
+      # "a.b.c" -> ["a", "b", "c"])
+      nextChar = state.getNextChar()
+      let isTableArrayDef = nextChar == '['
+      var tableNames: seq[string]
+      if isTableArrayDef:
+        tableNames = state.parseTableName(BracketType.double)
+      else:
+        state.pushBackChar(nextChar)
+        tableNames = state.parseTableName(BracketType.single)
+
+      # Now create the proper (empty) data structure: either a
+      # table or an array of tables. Note that both functions
+      # update the "curTableRef" variable: they have to, since
+      # the TOML specification says that any "key = value"
+      # statement that follows is a child of the table we're
+      # defining right now, and we use "curTableRef" as a
+      # reference to the table that gets every next key/value
+      # definition.
+      if isTableArrayDef:
+        createOrAppendTableArrayDef(state, tableNames)
+      else:
+        createTableDef(state, tableNames)
+
+    of '=':
+      raise(newTomlError(state, "key name missing"))
+    of '#', '.', ']':
+      raise(newTomlError(state,
+                         "unexpected character " & escape($nextChar)))
+    of '\0': # EOF
+      return
+    else:
+      # Everything else marks the presence of a "key = value" pattern
+      state.pushBackChar(nextChar)
+      parseKeyValuePair(state)
+
+proc parseString*(tomlStr: string, fileName: string = ""): TomlValueRef =
+  ## Parses a string of TOML formatted data into a TOML table. The optional
+  ## filename is used for error messages.
+  let strStream = newStringStream(tomlStr)
+  try:
+    result = parseStream(strStream, fileName)
+  finally:
+    strStream.close()
+
+proc parseFile*(f: File, fileName: string = ""): TomlValueRef =
+  ## Parses a file of TOML formatted data into a TOML table. The optional
+  ## filename is used for error messages.
+  let fStream = newFileStream(f)
+  try:
+    result = parseStream(fStream, fileName)
+  finally:
+    fStream.close()
+
+proc parseFile*(fileName: string): TomlValueRef =
+  ## Parses the file found at fileName with TOML formatted data into a TOML
+  ## table.
+  let fStream = newFileStream(fileName, fmRead)
+  if not isNil(fStream):
+    try:
+      result = parseStream(fStream, fileName)
+    finally:
+      fStream.close()
+  else:
+    raise newException(IOError, "cannot open: " & fileName)
+
+
+proc `$`*(val: TomlDate): string =
+  ## Converts the TOML date object into the ISO format read by the parser
+  result = ($val.year).align(4, '0') & "-" & ($val.month).align(2, '0') & "-" &
+    ($val.day).align(2, '0')
+
+proc `$`*(val: TomlTime): string =
+  ## Converts the TOML time object into the ISO format read by the parser
+  result = ($val.hour).align(2, '0') & ":" &
+    ($val.minute).align(2, '0') & ":" & ($val.second).align(2, '0') &
+    (if val.subsecond > 0: ("." & $val.subsecond) else: "")
+
+proc `$`*(val: TomlDateTime): string =
+  ## Converts the TOML date-time object into the ISO format read by the parser
+  result = $val.date & "T" & $val.time &
+    (if not val.shift: "" else: (
+      (if val.zoneHourShift == 0 and val.zoneMinuteShift == 0: "Z" else: (
+        ((if val.isShiftPositive: "+" else: "-") &
+        ($val.zoneHourShift).align(2, '0') & ":" &
+        ($val.zoneMinuteShift).align(2, '0'))
+      ))
+    ))
+
+proc toTomlString*(value: TomlValueRef): string
+
+proc `$`*(val: TomlValueRef): string =
+  ## Turns whatever value into a regular Nim value representtation
+  case val.kind
+  of TomlValueKind.None:
+    result = "nil"
+  of TomlValueKind.Int:
+    result = $val.intVal
+  of TomlValueKind.Float:
+    result = $val.floatVal
+  of TomlValueKind.Bool:
+    result = $val.boolVal
+  of TomlValueKind.Datetime:
+    result = $val.dateTimeVal
+  of TomlValueKind.Date:
+    result = $val.dateVal
+  of TomlValueKind.Time:
+    result = $val.timeVal
+  of TomlValueKind.String:
+    result = $val.stringVal
+  of TomlValueKind.Array:
+    result = ""
+    for elem in val.arrayVal:
+      result.add($(elem[]))
+  of TomlValueKind.Table:
+    result = val.toTomlString
+
+proc `$`*(val: TomlValue): string =
+  ## Turns whatever value into a type and value representation, used by ``dump``
+  case val.kind
+  of TomlValueKind.None:
+    result = "none()"
+  of TomlValueKind.Int:
+    result = "int(" & $val.intVal & ")"
+  of TomlValueKind.Float:
+    result = "float(" & $val.floatVal & ")"
+  of TomlValueKind.Bool:
+    result = "boolean(" & $val.boolVal & ")"
+  of TomlValueKind.Datetime:
+    result = "datetime(" & $val.dateTimeVal & ")"
+  of TomlValueKind.Date:
+    result = "date(" & $val.dateVal & ")"
+  of TomlValueKind.Time:
+    result = "time(" & $val.timeVal & ")"
+  of TomlValueKind.String:
+    result = "string(\"" & $val.stringVal & "\")"
+  of TomlValueKind.Array:
+    result = "array("
+    for elem in val.arrayVal:
+      result.add($(elem[]))
+    result.add(")")
+  of TomlValueKind.Table:
+    result = "table(" & $(len(val.tableVal)) & " elements)"
+
+proc dump*(table: TomlTableRef, indentLevel: int = 0) =
+  ## Dump out the entire table as it was parsed. This procedure is mostly
+  ## useful for debugging purposes
+  let space = spaces(indentLevel)
+  for key, val in pairs(table):
+    if val.kind == TomlValueKind.Table:
+      echo space & key & " = table"
+      dump(val.tableVal, indentLevel + 4)
+    elif (val.kind == TomlValueKind.Array and
+        val.arrayVal[0].kind == TomlValueKind.Table):
+      for idx, val in val.arrayVal:
+        echo space & key & "[" & $idx & "] = table"
+        dump(val.tableVal, indentLevel + 4)
+    else:
+      echo space & key & " = " & $(val[])
+
+import json, sequtils
+
+proc toJson*(value: TomlValueRef): JsonNode
+
+proc toJson*(table: TomlTableRef): JsonNode =
+  ## Converts a TOML table to a JSON node. This uses the format specified in
+  ## the validation suite for it's output:
+  ## https://github.com/BurntSushi/toml-test#example-json-encoding
+  result = newJObject()
+  for key, value in pairs(table):
+    result[key] = value.toJson
+
+proc toJson*(value: TomlValueRef): JsonNode =
+  ## Converts a TOML value to a JSON node. This uses the format specified in
+  ## the validation suite for it's output:
+  ## https://github.com/BurntSushi/toml-test#example-json-encoding
+  case value.kind:
+    of TomlValueKind.Int:
+      %*{"type": "integer", "value": $value.intVal}
+    of TomlValueKind.Float:
+      if classify(value.floatVal) == fcNan:
+        if value.forcedSign != Pos:
+          %*{"type": "float", "value": $value.floatVal}
+        else:
+          %*{"type": "float", "value": "+" & $value.floatVal}
+      else:
+        %*{"type": "float", "value": $value.floatVal}
+    of TomlValueKind.Bool:
+      %*{"type": "bool", "value": $value.boolVal}
+    of TomlValueKind.Datetime:
+      if value.dateTimeVal.shift == false:
+        %*{"type": "datetime-local", "value": $value.dateTimeVal}
+      else:
+        %*{"type": "datetime", "value": $value.dateTimeVal}
+    of TomlValueKind.Date:
+      %*{"type": "date", "value": $value.dateVal}
+    of TomlValueKind.Time:
+      %*{"type": "time", "value": $value.timeVal}
+    of TomlValueKind.String:
+      %*{"type": "string", "value": newJString(value.stringVal)}
+    of TomlValueKind.Array:
+      if value.arrayVal.len == 0:
+        when defined(newtestsuite):
+          %[]
+        else:
+          %*{"type": "array", "value": []}
+      elif value.arrayVal[0].kind == TomlValueKind.Table:
+        %value.arrayVal.map(toJson)
+      else:
+        when defined(newtestsuite):
+          %*value.arrayVal.map(toJson)
+        else:
+          %*{"type": "array", "value": value.arrayVal.map(toJson)}
+    of TomlValueKind.Table:
+      value.tableVal.toJson
+    of TomlValueKind.None:
+      %*{"type": "ERROR"}
+
+proc toKey(str: string): string =
+  for c in str:
+    if (c notin {'a'..'z', 'A'..'Z', '0'..'9', '_', '-'}):
+      return "\"" & str & "\""
+  str
+
+
+proc toTomlString*(value: TomlTableRef, parents = ""): string =
+  ## Converts a TOML table to a TOML formatted string for output to a file.
+  result = ""
+  var subtables: seq[tuple[key: string, value: TomlValueRef]] = @[]
+  for key, value in pairs(value):
+    block outer:
+      if value.kind == TomlValueKind.Table:
+        subtables.add((key: key, value: value))
+      elif value.kind == TomlValueKind.Array and
+           value.arrayVal.len > 0 and
+           value.arrayVal[0].kind == TomlValueKind.Table:
+        let tables = value.arrayVal.map(toTomlString)
+        for table in tables:
+          result = result & "[[" & key & "]]\n" & table & "\n"
+      else:
+        result = result & key.toKey & " = " & toTomlString(value) & "\n"
+  for kv in subtables:
+    let fullKey = (if parents.len > 0: parents & "." else: "") & kv.key.toKey
+    block outer:
+      for ikey, ivalue in pairs(kv.value.tableVal):
+        if ivalue.kind != TomlValueKind.Table:
+          result = result & "[" & fullKey & "]\n" &
+            kv.value.tableVal.toTomlString(fullKey) & "\n"
+          break outer
+      result = result & kv.value.tableVal.toTomlString(fullKey)
+
+proc toTomlString*(value: TomlValueRef): string =
+  ## Converts a TOML value to a TOML formatted string for output to a file.
+  case value.kind:
+  of TomlValueKind.Int: $value.intVal
+  of TomlValueKind.Float: $value.floatVal
+  of TomlValueKind.Bool: $value.boolVal
+  of TomlValueKind.Datetime: $value.dateTimeVal
+  of TomlValueKind.String: "\"" & value.stringVal & "\""
+  of TomlValueKind.Array:
+    if value.arrayVal.len == 0:
+      "[]"
+    elif value.arrayVal[0].kind == TomlValueKind.Table:
+      value.arrayVal.map(toTomlString).join("\n")
+    else:
+      "[" & value.arrayVal.map(toTomlString).join(", ") & "]"
+  of TomlValueKind.Table: value.tableVal.toTomlString
+  else:
+    "UNKNOWN"
+
+proc newTString*(s: string): TomlValueRef =
+  ## Creates a new `TomlValueKind.String TomlValueRef`.
+  TomlValueRef(kind: TomlValueKind.String, stringVal: s)
+
+proc newTInt*(n: int64): TomlValueRef =
+  ## Creates a new `TomlValueKind.Int TomlValueRef`.
+  TomlValueRef(kind: TomlValueKind.Int, intVal: n)
+
+proc newTFloat*(n: float): TomlValueRef =
+  ## Creates a new `TomlValueKind.Float TomlValueRef`.
+  TomlValueRef(kind: TomlValueKind.Float, floatVal: n)
+
+proc newTBool*(b: bool): TomlValueRef =
+  ## Creates a new `TomlValueKind.Bool TomlValueRef`.
+  TomlValueRef(kind: TomlValueKind.Bool, boolVal: b)
+
+proc newTNull*(): TomlValueRef =
+  ## Creates a new `JNull TomlValueRef`.
+  TomlValueRef(kind: TomlValueKind.None)
+
+proc newTTable*(): TomlValueRef =
+  ## Creates a new `TomlValueKind.Table TomlValueRef`
+  result = TomlValueRef(kind: TomlValueKind.Table)
+  new(result.tableVal)
+  result.tableVal[] = initOrderedTable[string, TomlValueRef](4)
+
+proc newTArray*(): TomlValueRef =
+  ## Creates a new `TomlValueKind.Array TomlValueRef`
+  TomlValueRef(kind: TomlValueKind.Array, arrayVal: @[])
+
+proc getStr*(n: TomlValueRef, default: string = ""): string =
+  ## Retrieves the string value of a `TomlValueKind.String TomlValueRef`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``TomlValueKind.String``, or if ``n`` is nil.
+  if n.isNil or n.kind != TomlValueKind.String: return default
+  else: return n.stringVal
+
+proc getInt*(n: TomlValueRef, default: int = 0): int =
+  ## Retrieves the int value of a `TomlValueKind.Int TomlValueRef`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``TomlValueKind.Int``, or if ``n`` is nil.
+  if n.isNil or n.kind != TomlValueKind.Int: return default
+  else: return int(n.intVal)
+
+proc getBiggestInt*(n: TomlValueRef, default: int64 = 0): int64 =
+  ## Retrieves the int64 value of a `TomlValueKind.Int TomlValueRef`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``TomlValueKind.Int``, or if ``n`` is nil.
+  if n.isNil or n.kind != TomlValueKind.Int: return default
+  else: return n.intVal
+
+proc getFloat*(n: TomlValueRef, default: float = 0.0): float =
+  ## Retrieves the float value of a `TomlValueKind.Float TomlValueRef`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``TomlValueKind.Float`` or ``TomlValueKind.Int``, or if ``n`` is nil.
+  if n.isNil: return default
+  case n.kind
+  of TomlValueKind.Float: return n.floatVal
+  of TomlValueKind.Int: return float(n.intVal)
+  else: return default
+
+proc getBool*(n: TomlValueRef, default: bool = false): bool =
+  ## Retrieves the bool value of a `TomlValueKind.Bool TomlValueRef`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``TomlValueKind.Bool``, or if ``n`` is nil.
+  if n.isNil or n.kind != TomlValueKind.Bool: return default
+  else: return n.boolVal
+
+proc getTable*(n: TomlValueRef, default = new(TomlTableRef)): TomlTableRef =
+  ## Retrieves the key, value pairs of a `TomlValueKind.Table TomlValueRef`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``TomlValueKind.Table``, or if ``n`` is nil.
+  if n.isNil or n.kind != TomlValueKind.Table: return default
+  else: return n.tableVal
+
+proc getElems*(n: TomlValueRef, default: seq[TomlValueRef] = @[]): seq[TomlValueRef] =
+  ## Retrieves the int value of a `TomlValueKind.Array TomlValueRef`.
+  ##
+  ## Returns ``default`` if ``n`` is not a ``TomlValueKind.Array``, or if ``n`` is nil.
+  if n.isNil or n.kind != TomlValueKind.Array: return default
+  else: return n.arrayVal
+
+proc add*(father, child: TomlValueRef) =
+  ## Adds `child` to a TomlValueKind.Array node `father`.
+  assert father.kind == TomlValueKind.Array
+  father.arrayVal.add(child)
+
+proc add*(obj: TomlValueRef, key: string, val: TomlValueRef) =
+  ## Sets a field from a `TomlValueKind.Table`.
+  assert obj.kind == TomlValueKind.Table
+  obj.tableVal[key] = val
+
+proc `?`*(s: string): TomlValueRef =
+  ## Generic constructor for TOML data. Creates a new `TomlValueKind.String TomlValueRef`.
+  TomlValueRef(kind: TomlValueKind.String, stringVal: s)
+
+proc `?`*(n: int64): TomlValueRef =
+  ## Generic constructor for TOML data. Creates a new `TomlValueKind.Int TomlValueRef`.
+  TomlValueRef(kind: TomlValueKind.Int, intVal: n)
+
+proc `?`*(n: float): TomlValueRef =
+  ## Generic constructor for TOML data. Creates a new `TomlValueKind.Float TomlValueRef`.
+  TomlValueRef(kind: TomlValueKind.Float, floatVal: n)
+
+proc `?`*(b: bool): TomlValueRef =
+  ## Generic constructor for TOML data. Creates a new `TomlValueKind.Bool TomlValueRef`.
+  TomlValueRef(kind: TomlValueKind.Bool, boolVal: b)
+
+proc `?`*(keyVals: openArray[tuple[key: string, val: TomlValueRef]]): TomlValueRef =
+  ## Generic constructor for TOML data. Creates a new `TomlValueKind.Table TomlValueRef`
+  if keyVals.len == 0: return newTArray()
+  result = newTTable()
+  for key, val in items(keyVals): result.tableVal[key] = val
+
+template `?`*(j: TomlValueRef): TomlValueRef = j
+
+proc `?`*[T](elements: openArray[T]): TomlValueRef =
+  ## Generic constructor for TOML data. Creates a new `TomlValueKind.Array TomlValueRef`
+  result = newTArray()
+  for elem in elements: result.add(?elem)
+
+when false:
+  # For 'consistency' we could do this, but that only pushes people further
+  # into that evil comfort zone where they can use Nim without understanding it
+  # causing problems later on.
+  proc `?`*(elements: set[bool]): TomlValueRef =
+    ## Generic constructor for TOML data. Creates a new `TomlValueKind.Table TomlValueRef`.
+    ## This can only be used with the empty set ``{}`` and is supported
+    ## to prevent the gotcha ``%*{}`` which used to produce an empty
+    ## TOML array.
+    result = newTTable()
+    assert false notin elements, "usage error: only empty sets allowed"
+    assert true notin elements, "usage error: only empty sets allowed"
+
+proc `?`*(o: object): TomlValueRef =
+  ## Generic constructor for TOML data. Creates a new `TomlValueKind.Table TomlValueRef`
+  result = newTTable()
+  for k, v in o.fieldPairs: result[k] = ?v
+
+proc `?`*(o: ref object): TomlValueRef =
+  ## Generic constructor for TOML data. Creates a new `TomlValueKind.Table TomlValueRef`
+  if o.isNil:
+    result = newTNull()
+  else:
+    result = ?(o[])
+
+proc `?`*(o: enum): TomlValueRef =
+  ## Construct a TomlValueRef that represents the specified enum value as a
+  ## string. Creates a new ``TomlValueKind.String TomlValueRef``.
+  result = ?($o)
+
+import macros
+
+proc toToml(x: NimNode): NimNode {.compileTime.} =
+  case x.kind
+  of nnkBracket: # array
+    if x.len == 0: return newCall(bindSym"newTArray")
+    result = newNimNode(nnkBracket)
+    for i in 0 ..< x.len:
+      result.add(toToml(x[i]))
+    result = newCall(bindSym("?", brOpen), result)
+  of nnkTableConstr: # object
+    if x.len == 0: return newCall(bindSym"newTTable")
+    result = newNimNode(nnkTableConstr)
+    for i in 0 ..< x.len:
+      x[i].expectKind nnkExprColonExpr
+      result.add newTree(nnkExprColonExpr, x[i][0], toToml(x[i][1]))
+    result = newCall(bindSym("?", brOpen), result)
+  of nnkCurly: # empty object
+    x.expectLen(0)
+    result = newCall(bindSym"newTTable")
+  of nnkNilLit:
+    result = newCall(bindSym"newTNull")
+  else:
+    result = newCall(bindSym("?", brOpen), x)
+
+macro `?*`*(x: untyped): untyped =
+  ## Convert an expression to a TomlValueRef directly, without having to specify
+  ## `?` for every element.
+  result = toToml(x)
+  echo result.repr
+
+proc toTomlValue(x: NimNode): NimNode {.compileTime.} =
+  newCall(bindSym("?", brOpen), x)
+
+proc toTomlNew(x: NimNode): NimNode {.compileTime.} =
+  echo x.treeRepr
+  var
+    i = 0
+    curTable: NimNode = nil
+  while i < x.len:
+    echo x[i].kind
+    case x[i].kind:
+    of nnkAsgn:
+      if curTable.isNil:
+        curTable = newNimNode(nnkTableConstr)
+        result = curTable
+      curTable.add newTree(nnkExprColonExpr, newLit($x[i][0]), toTomlValue(x[i][1]))
+    of nnkBracket:
+      if curTable.isNil:
+        curTable = newNimNode(nnkTableConstr)
+        result = curTable
+      else:
+        var table = newNimNode(nnkTableConstr)
+        result.add newTree(nnkExprColonExpr, newLit($x[i][0]), newCall(bindSym("?", brOpen), table))
+        curTable = table
+    else: discard
+    i += 1
+  result = newCall(bindSym("?", brOpen), result)
+
+macro `parseToml`*(x: untyped): untyped =
+  ## Convert an expression to a TomlValueRef directly, without having to specify
+  ## `?` for every element.
+  result = toTomlNew(x)
+  echo result.repr
+
+func `==`* (a, b: TomlValueRef): bool =
+  ## Check two nodes for equality
+  if a.isNil:
+    if b.isNil: return true
+    return false
+  elif b.isNil or a.kind != b.kind:
+    return false
+  else:
+    case a.kind
+    of TomlValueKind.String:
+      result = a.stringVal == b.stringVal
+    of TomlValueKind.Int:
+      result = a.intVal == b.intVal
+    of TomlValueKind.Float:
+      result = a.floatVal == b.floatVal
+    of TomlValueKind.Bool:
+      result = a.boolVal == b.boolVal
+    of TomlValueKind.None:
+      result = true
+    of TomlValueKind.Array:
+      result = a.arrayVal == b.arrayVal
+    of TomlValueKind.Table:
+      # we cannot use OrderedTable's equality here as
+      # the order does not matter for equality here.
+      if a.tableVal.len != b.tableVal.len: return false
+      for key, val in a.tableVal:
+        if not b.tableVal.hasKey(key): return false
+        {.noSideEffect.}:
+          if b.tableVal[key] != val: return false
+      result = true
+    of TomlValueKind.DateTime:
+      result =
+        a.dateTimeVal.date.year == b.dateTimeVal.date.year and
+        a.dateTimeVal.date.month == b.dateTimeVal.date.month and
+        a.dateTimeVal.date.day == b.dateTimeVal.date.day and
+        a.dateTimeVal.time.hour == b.dateTimeVal.time.hour and
+        a.dateTimeVal.time.minute == b.dateTimeVal.time.minute and
+        a.dateTimeVal.time.second == b.dateTimeVal.time.second and
+        a.dateTimeVal.time.subsecond == b.dateTimeVal.time.subsecond and
+        a.dateTimeVal.shift == b.dateTimeVal.shift and
+        (a.dateTimeVal.shift == true and
+          (a.dateTimeVal.isShiftPositive == b.dateTimeVal.isShiftPositive and
+          a.dateTimeVal.zoneHourShift == b.dateTimeVal.zoneHourShift and
+          a.dateTimeVal.zoneMinuteShift == b.dateTimeVal.zoneMinuteShift)) or
+        a.dateTimeVal.shift == false
+    of TomlValueKind.Date:
+      result =
+        a.dateVal.year == b.dateVal.year and
+        a.dateVal.month == b.dateVal.month and
+        a.dateVal.day == b.dateVal.day
+    of TomlValueKind.Time:
+      result =
+        a.timeVal.hour == b.timeVal.hour and
+        a.timeVal.minute == b.timeVal.minute and
+        a.timeVal.second == b.timeVal.second and
+        a.timeVal.subsecond == b.timeVal.subsecond
+
+import hashes
+
+proc hash*(n: OrderedTable[string, TomlValueRef]): Hash {.noSideEffect.}
+
+proc hash*(n: TomlValueRef): Hash {.noSideEffect.} =
+  ## Compute the hash for a TOML node
+  case n.kind
+  of TomlValueKind.Array:
+    result = hash(n.arrayVal)
+  of TomlValueKind.Table:
+    result = hash(n.tableVal[])
+  of TomlValueKind.Int:
+    result = hash(n.intVal)
+  of TomlValueKind.Float:
+    result = hash(n.floatVal)
+  of TomlValueKind.Bool:
+    result = hash(n.boolVal.int)
+  of TomlValueKind.String:
+    result = hash(n.stringVal)
+  of TomlValueKind.None:
+    result = Hash(0)
+  of TomlValueKind.DateTime:
+    result = hash($n.dateTimeVal)
+  of TomlValueKind.Date:
+    result = hash($n.dateVal)
+  of TomlValueKind.Time:
+    result = hash($n.timeVal)
+
+proc hash*(n: OrderedTable[string, TomlValueRef]): Hash =
+  for key, val in n:
+    result = result xor (hash(key) !& hash(val))
+  result = !$result
+
+proc len*(n: TomlValueRef): int =
+  ## If `n` is a `TomlValueKind.Array`, it returns the number of elements.
+  ## If `n` is a `TomlValueKind.Table`, it returns the number of pairs.
+  ## Else it returns 0.
+  case n.kind
+  of TomlValueKind.Array: result = n.arrayVal.len
+  of TomlValueKind.Table: result = n.tableVal.len
+  else: discard
+
+proc `[]`*(node: TomlValueRef, name: string): TomlValueRef {.inline.} =
+  ## Gets a field from a `TomlValueKind.Table`, which must not be nil.
+  ## If the value at `name` does not exist, raises KeyError.
+  assert(not isNil(node))
+  assert(node.kind == TomlValueKind.Table)
+  result = node.tableVal[name]
+
+proc `[]`*(node: TomlValueRef, index: int): TomlValueRef {.inline.} =
+  ## Gets the node at `index` in an Array. Result is undefined if `index`
+  ## is out of bounds, but as long as array bound checks are enabled it will
+  ## result in an exception.
+  assert(not isNil(node))
+  assert(node.kind == TomlValueKind.Array)
+  return node.arrayVal[index]
+
+proc hasKey*(node: TomlValueRef, key: string): bool =
+  ## Checks if `key` exists in `node`.
+  assert(node.kind == TomlValueKind.Table)
+  result = node.tableVal.hasKey(key)
+
+proc contains*(node: TomlValueRef, key: string): bool =
+  ## Checks if `key` exists in `node`.
+  assert(node.kind == TomlValueKind.Table)
+  node.tableVal.hasKey(key)
+
+proc contains*(node: TomlValueRef, val: TomlValueRef): bool =
+  ## Checks if `val` exists in array `node`.
+  assert(node.kind == TomlValueKind.Array)
+  find(node.arrayVal, val) >= 0
+
+proc existsKey*(node: TomlValueRef, key: string): bool {.deprecated.} = node.hasKey(key)
+  ## Deprecated for `hasKey`
+
+proc `[]=`*(obj: TomlValueRef, key: string, val: TomlValueRef) {.inline.} =
+  ## Sets a field from a `TomlValueKind.Table`.
+  assert(obj.kind == TomlValueKind.Table)
+  obj.tableVal[key] = val
+
+proc `{}`*(node: TomlValueRef, keys: varargs[string]): TomlValueRef =
+  ## Traverses the node and gets the given value. If any of the
+  ## keys do not exist, returns ``nil``. Also returns ``nil`` if one of the
+  ## intermediate data structures is not an object.
+  result = node
+  for key in keys:
+    if isNil(result) or result.kind != TomlValueKind.Table:
+      return nil
+    result = result.tableVal.getOrDefault(key)
+
+proc getOrDefault*(node: TomlValueRef, key: string): TomlValueRef =
+  ## Gets a field from a `node`. If `node` is nil or not an object or
+  ## value at `key` does not exist, returns nil
+  if not isNil(node) and node.kind == TomlValueKind.Table:
+    result = node.tableVal.getOrDefault(key)
+
+template simpleGetOrDefault*{`{}`(node, [key])}(node: TomlValueRef, key: string): TomlValueRef = node.getOrDefault(key)
+
+proc `{}=`*(node: TomlValueRef, keys: varargs[string], value: TomlValueRef) =
+  ## Traverses the node and tries to set the value at the given location
+  ## to ``value``. If any of the keys are missing, they are added.
+  var node = node
+  for i in 0..(keys.len-2):
+    if not node.hasKey(keys[i]):
+      node[keys[i]] = newTTable()
+    node = node[keys[i]]
+  node[keys[keys.len-1]] = value
+
+proc delete*(obj: TomlValueRef, key: string) =
+  ## Deletes ``obj[key]``.
+  assert(obj.kind == TomlValueKind.Table)
+  if not obj.tableVal.hasKey(key):
+    raise newException(IndexDefect, "key not in object")
+  obj.tableVal.del(key)
+
+proc copy*(p: TomlValueRef): TomlValueRef =
+  ## Performs a deep copy of `a`.
+  case p.kind
+  of TomlValueKind.String:
+    result = newTString(p.stringVal)
+  of TomlValueKind.Int:
+    result = newTInt(p.intVal)
+  of TomlValueKind.Float:
+    result = newTFloat(p.floatVal)
+  of TomlValueKind.Bool:
+    result = newTBool(p.boolVal)
+  of TomlValueKind.None:
+    result = newTNull()
+  of TomlValueKind.Table:
+    result = newTTable()
+    for key, val in pairs(p.tableVal):
+      result.tableVal[key] = copy(val)
+  of TomlValueKind.Array:
+    result = newTArray()
+    for i in items(p.arrayVal):
+      result.arrayVal.add(copy(i))
+  of TomlValueKind.DateTime:
+    new(result)
+    result[] = p[]
+  of TomlValueKind.Date:
+    new(result)
+    result[] = p[]
+  of TomlValueKind.Time:
+    new(result)
+    result[] = p[]