summary refs log tree commit diff stats
path: root/lib/pure/parsecfg.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/parsecfg.nim')
-rw-r--r--[-rwxr-xr-x]lib/pure/parsecfg.nim799
1 files changed, 553 insertions, 246 deletions
diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim
index 4f8f5347f..8a43daf54 100755..100644
--- a/lib/pure/parsecfg.nim
+++ b/lib/pure/parsecfg.nim
@@ -1,352 +1,659 @@
 #
 #
-#            Nimrod's Runtime Library
+#            Nim's Runtime Library
 #        (c) Copyright 2010 Andreas Rumpf
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
 #
 
-## The ``parsecfg`` module implements a high performance configuration file 
-## parser. The configuration file's syntax is similar to the Windows ``.ini`` 
-## format, but much more powerful, as it is not a line based parser. String 
-## literals, raw string literals and triple quoted string literals are supported 
-## as in the Nimrod programming language.
-
-## This is an example of how a configuration file may look like:
+## The `parsecfg` module implements a high performance configuration file
+## parser. The configuration file's syntax is similar to the Windows `.ini`
+## format, but much more powerful, as it is not a line based parser. String
+## literals, raw string literals and triple quoted string literals are supported
+## as in the Nim programming language.
+##
+## Example of how a configuration file may look like:
 ##
-## .. include:: doc/mytest.cfg
+## .. include:: ../../doc/mytest.cfg
 ##     :literal:
-## The file ``examples/parsecfgex.nim`` demonstrates how to use the 
-## configuration file parser:
 ##
-## .. code-block:: nimrod
-##     :file: examples/parsecfgex.nim
-
-
-import 
-  hashes, strutils, lexbase, streams
-
-type 
-  TCfgEventKind* = enum ## enumation of all events that may occur when parsing
-    cfgEof,             ## end of file reached
-    cfgSectionStart,    ## a ``[section]`` has been parsed
-    cfgKeyValuePair,    ## a ``key=value`` pair has been detected
-    cfgOption,          ## a ``--key=value`` command line option
-    cfgError            ## an error ocurred during parsing
-    
-  TCfgEvent* = object of TObject ## describes a parsing event
-    case kind*: TCfgEventKind    ## the kind of the event
-    of cfgEof: nil
-    of cfgSectionStart: 
-      section*: string           ## `section` contains the name of the 
-                                 ## parsed section start (syntax: ``[section]``)
-    of cfgKeyValuePair, cfgOption: 
-      key*, value*: string       ## contains the (key, value) pair if an option
-                                 ## of the form ``--key: value`` or an ordinary
-                                 ## ``key= value`` pair has been parsed.
-                                 ## ``value==""`` if it was not specified in the
-                                 ## configuration file.
-    of cfgError:                 ## the parser encountered an error: `msg`
-      msg*: string               ## contains the error message. No exceptions
-                                 ## are thrown if a parse error occurs.
-  
-  TTokKind = enum 
-    tkInvalid, tkEof,        
-    tkSymbol, tkEquals, tkColon, tkBracketLe, tkBracketRi, tkDashDash
-  TToken {.final.} = object  # a token
-    kind: TTokKind           # the type of the token
-    literal: string          # the parsed (string) literal
-  
-  TParserState = enum 
-    startState # , commaState # not yet used
-  TCfgParser* = object of TBaseLexer ## the parser object.
-    tok: TToken
-    state: TParserState
-    filename: string
+## Here is an example of how to use the configuration file parser:
+runnableExamples("-r:off"):
+  import std/[strutils, streams]
 
-proc open*(c: var TCfgParser, input: PStream, filename: string)
-  ## initializes the parser with an input stream. `Filename` is only used
-  ## for nice error messages.
+  let configFile = "example.ini"
+  var f = newFileStream(configFile, fmRead)
+  assert f != nil, "cannot open " & configFile
+  var p: CfgParser
+  open(p, f, configFile)
+  while true:
+    var e = next(p)
+    case e.kind
+    of cfgEof: break
+    of cfgSectionStart:   ## a `[section]` has been parsed
+      echo "new section: " & e.section
+    of cfgKeyValuePair:
+      echo "key-value-pair: " & e.key & ": " & e.value
+    of cfgOption:
+      echo "command: " & e.key & ": " & e.value
+    of cfgError:
+      echo e.msg
+  close(p)
 
-proc close*(c: var TCfgParser)
-  ## closes the parser `c` and its associated input stream.
+##[
+## Configuration file example
+]##
 
-proc next*(c: var TCfgParser): TCfgEvent
-  ## retrieves the first/next event. This controls the parser.
+##     ```none
+##     charset = "utf-8"
+##     [Package]
+##     name = "hello"
+##     --threads:on
+##     [Author]
+##     name = "nim-lang"
+##     website = "nim-lang.org"
+##     ```
 
-proc getColumn*(c: TCfgParser): int
-  ## get the current column the parser has arrived at.
+##[
+## Creating a configuration file
+]##
 
-proc getLine*(c: TCfgParser): int
-  ## get the current line the parser has arrived at.
-  
-proc getFilename*(c: TCfgParser): string
-  ## get the filename of the file that the parser processes.
+runnableExamples:
+  var dict = newConfig()
+  dict.setSectionKey("","charset", "utf-8")
+  dict.setSectionKey("Package", "name", "hello")
+  dict.setSectionKey("Package", "--threads", "on")
+  dict.setSectionKey("Author", "name", "nim-lang")
+  dict.setSectionKey("Author", "website", "nim-lang.org")
+  assert $dict == """
+charset=utf-8
+[Package]
+name=hello
+--threads:on
+[Author]
+name=nim-lang
+website=nim-lang.org
+"""
 
-proc errorStr*(c: TCfgParser, msg: string): string
-  ## returns a properly formated error message containing current line and
-  ## column information.
+##[
+## Reading a configuration file
+]##
+
+runnableExamples("-r:off"):
+  let dict = loadConfig("config.ini")
+  let charset = dict.getSectionValue("","charset")
+  let threads = dict.getSectionValue("Package","--threads")
+  let pname = dict.getSectionValue("Package","name")
+  let name = dict.getSectionValue("Author","name")
+  let website = dict.getSectionValue("Author","website")
+  echo pname & "\n" & name & "\n" & website
+
+##[
+## Modifying a configuration file
+]##
+
+runnableExamples("-r:off"):
+  var dict = loadConfig("config.ini")
+  dict.setSectionKey("Author", "name", "nim-lang")
+  dict.writeConfig("config.ini")
+
+##[
+## Deleting a section key in a configuration file
+]##
+
+runnableExamples("-r:off"):
+  var dict = loadConfig("config.ini")
+  dict.delSectionKey("Author", "website")
+  dict.writeConfig("config.ini")
+
+##[
+## Supported INI File structure
+]##
+
+# taken from https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+runnableExamples:
+  import std/streams
+
+  var dict = loadConfig(newStringStream("""[Simple Values]
+    key=value
+    spaces in keys=allowed
+    spaces in values=allowed as well
+    spaces around the delimiter = obviously
+    you can also use : to delimit keys from values
+    [All Values Are Strings]
+    values like this: 19990429
+    or this: 3.14159265359
+    are they treated as numbers : no
+    integers floats and booleans are held as: strings
+    can use the API to get converted values directly: true
+    [No Values]
+    key_without_value
+    # empty string value is not allowed =
+    [ Seletion A   ]
+    space around section name will be ignored
+    [You can use comments]
+    # like this
+    ; or this
+    # By default only in an empty line.
+    # Inline comments can be harmful because they prevent users
+    # from using the delimiting characters as parts of values.
+    # That being said, this can be customized.
+        [Sections Can Be Indented]
+            can_values_be_as_well = True
+            does_that_mean_anything_special = False
+            purpose = formatting for readability
+            # Did I mention we can indent comments, too?
+    """)
+  )
+
+  let section1 = "Simple Values"
+  assert dict.getSectionValue(section1, "key") == "value"
+  assert dict.getSectionValue(section1, "spaces in keys") == "allowed"
+  assert dict.getSectionValue(section1, "spaces in values") == "allowed as well"
+  assert dict.getSectionValue(section1, "spaces around the delimiter") == "obviously"
+  assert dict.getSectionValue(section1, "you can also use") == "to delimit keys from values"
+
+  let section2 = "All Values Are Strings"
+  assert dict.getSectionValue(section2, "values like this") == "19990429"
+  assert dict.getSectionValue(section2, "or this") == "3.14159265359"
+  assert dict.getSectionValue(section2, "are they treated as numbers") == "no"
+  assert dict.getSectionValue(section2, "integers floats and booleans are held as") == "strings"
+  assert dict.getSectionValue(section2, "can use the API to get converted values directly") == "true"
+
+  let section3 = "Seletion A"
+  assert dict.getSectionValue(section3, 
+    "space around section name will be ignored", "not an empty value") == ""
+
+  let section4 = "Sections Can Be Indented"
+  assert dict.getSectionValue(section4, "can_values_be_as_well") == "True"
+  assert dict.getSectionValue(section4, "does_that_mean_anything_special") == "False"
+  assert dict.getSectionValue(section4, "purpose") == "formatting for readability"
+
+import std/[strutils, lexbase, streams, tables]
+import std/private/decode_helpers
+import std/private/since
 
+when defined(nimPreviewSlimSystem):
+  import std/syncio
+
+include "system/inclrtl"
+
+
+type
+  CfgEventKind* = enum ## enumeration of all events that may occur when parsing
+    cfgEof,            ## end of file reached
+    cfgSectionStart,   ## a `[section]` has been parsed
+    cfgKeyValuePair,   ## a `key=value` pair has been detected
+    cfgOption,         ## a `--key=value` command line option
+    cfgError           ## an error occurred during parsing
+
+  CfgEvent* = object of RootObj ## describes a parsing event
+    case kind*: CfgEventKind    ## the kind of the event
+    of cfgEof: nil
+    of cfgSectionStart:
+      section*: string          ## `section` contains the name of the
+                                ## parsed section start (syntax: `[section]`)
+    of cfgKeyValuePair, cfgOption:
+      key*, value*: string      ## contains the (key, value) pair if an option
+                                ## of the form `--key: value` or an ordinary
+                                ## `key= value` pair has been parsed.
+                                ## `value==""` if it was not specified in the
+                                ## configuration file.
+    of cfgError:                ## the parser encountered an error: `msg`
+      msg*: string              ## contains the error message. No exceptions
+                                ## are thrown if a parse error occurs.
+
+  TokKind = enum
+    tkInvalid, tkEof,
+    tkSymbol, tkEquals, tkColon, tkBracketLe, tkBracketRi, tkDashDash
+  Token = object    # a token
+    kind: TokKind   # the type of the token
+    literal: string # the parsed (string) literal
+
+  CfgParser* = object of BaseLexer ## the parser object.
+    tok: Token
+    filename: string
 
 # implementation
 
-const 
-  SymChars: TCharSet = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'} 
-  
-proc rawGetTok(c: var TCfgParser, tok: var TToken)
-proc open(c: var TCfgParser, input: PStream, filename: string) = 
+const
+  SymChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', ' ', '\x80'..'\xFF', '.', '/', '\\', '-'}
+
+proc rawGetTok(c: var CfgParser, tok: var Token) {.gcsafe.}
+
+proc open*(c: var CfgParser, input: Stream, filename: string,
+           lineOffset = 0) {.rtl, extern: "npc$1".} =
+  ## Initializes the parser with an input stream. `Filename` is only used
+  ## for nice error messages. `lineOffset` can be used to influence the line
+  ## number information in the generated error messages.
   lexbase.open(c, input)
   c.filename = filename
-  c.state = startState
   c.tok.kind = tkInvalid
   c.tok.literal = ""
+  inc(c.lineNumber, lineOffset)
   rawGetTok(c, c.tok)
-  
-proc close(c: var TCfgParser) = 
+
+proc close*(c: var CfgParser) {.rtl, extern: "npc$1".} =
+  ## Closes the parser `c` and its associated input stream.
   lexbase.close(c)
 
-proc getColumn(c: TCfgParser): int = 
-  result = getColNumber(c, c.bufPos)
+proc getColumn*(c: CfgParser): int {.rtl, extern: "npc$1".} =
+  ## Gets the current column the parser has arrived at.
+  result = getColNumber(c, c.bufpos)
 
-proc getLine(c: TCfgParser): int = 
-  result = c.linenumber
+proc getLine*(c: CfgParser): int {.rtl, extern: "npc$1".} =
+  ## Gets the current line the parser has arrived at.
+  result = c.lineNumber
 
-proc getFilename(c: TCfgParser): string = 
+proc getFilename*(c: CfgParser): string {.rtl, extern: "npc$1".} =
+  ## Gets the filename of the file that the parser processes.
   result = c.filename
 
-proc handleHexChar(c: var TCfgParser, xi: var int) = 
-  case c.buf[c.bufpos]
-  of '0'..'9': 
-    xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('0'))
-    inc(c.bufpos)
-  of 'a'..'f': 
-    xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('a') + 10)
-    inc(c.bufpos)
-  of 'A'..'F': 
-    xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10)
-    inc(c.bufpos)
-  else: 
-    nil
-
-proc handleDecChars(c: var TCfgParser, xi: var int) = 
-  while c.buf[c.bufpos] in {'0'..'9'}: 
+proc handleDecChars(c: var CfgParser, xi: var int) =
+  while c.buf[c.bufpos] in {'0'..'9'}:
     xi = (xi * 10) + (ord(c.buf[c.bufpos]) - ord('0'))
     inc(c.bufpos)
 
-proc getEscapedChar(c: var TCfgParser, tok: var TToken) = 
-  inc(c.bufpos)               # skip '\'
+proc getEscapedChar(c: var CfgParser, tok: var Token) =
+  inc(c.bufpos) # skip '\'
   case c.buf[c.bufpos]
-  of 'n', 'N': 
+  of 'n', 'N':
     add(tok.literal, "\n")
-    Inc(c.bufpos)
-  of 'r', 'R', 'c', 'C': 
+    inc(c.bufpos)
+  of 'r', 'R', 'c', 'C':
     add(tok.literal, '\c')
-    Inc(c.bufpos)
-  of 'l', 'L': 
+    inc(c.bufpos)
+  of 'l', 'L':
     add(tok.literal, '\L')
-    Inc(c.bufpos)
-  of 'f', 'F': 
+    inc(c.bufpos)
+  of 'f', 'F':
     add(tok.literal, '\f')
     inc(c.bufpos)
-  of 'e', 'E': 
+  of 'e', 'E':
     add(tok.literal, '\e')
-    Inc(c.bufpos)
-  of 'a', 'A': 
+    inc(c.bufpos)
+  of 'a', 'A':
     add(tok.literal, '\a')
-    Inc(c.bufpos)
-  of 'b', 'B': 
+    inc(c.bufpos)
+  of 'b', 'B':
     add(tok.literal, '\b')
-    Inc(c.bufpos)
-  of 'v', 'V': 
+    inc(c.bufpos)
+  of 'v', 'V':
     add(tok.literal, '\v')
-    Inc(c.bufpos)
-  of 't', 'T': 
+    inc(c.bufpos)
+  of 't', 'T':
     add(tok.literal, '\t')
-    Inc(c.bufpos)
-  of '\'', '\"': 
+    inc(c.bufpos)
+  of '\'', '"':
     add(tok.literal, c.buf[c.bufpos])
-    Inc(c.bufpos)
-  of '\\': 
+    inc(c.bufpos)
+  of '\\':
     add(tok.literal, '\\')
-    Inc(c.bufpos)
-  of 'x', 'X': 
+    inc(c.bufpos)
+  of 'x', 'X':
     inc(c.bufpos)
     var xi = 0
-    handleHexChar(c, xi)
-    handleHexChar(c, xi)
-    add(tok.literal, Chr(xi))
-  of '0'..'9': 
+    if handleHexChar(c.buf[c.bufpos], xi):
+      inc(c.bufpos)
+      if handleHexChar(c.buf[c.bufpos], xi):
+        inc(c.bufpos)
+    add(tok.literal, chr(xi))
+  of '0'..'9':
     var xi = 0
     handleDecChars(c, xi)
-    if (xi <= 255): add(tok.literal, Chr(xi))
+    if (xi <= 255): add(tok.literal, chr(xi))
     else: tok.kind = tkInvalid
   else: tok.kind = tkInvalid
-  
-proc HandleCRLF(c: var TCfgParser, pos: int): int = 
+
+proc handleCRLF(c: var CfgParser, pos: int): int =
   case c.buf[pos]
-  of '\c': result = lexbase.HandleCR(c, pos)
-  of '\L': result = lexbase.HandleLF(c, pos)
+  of '\c': result = lexbase.handleCR(c, pos)
+  of '\L': result = lexbase.handleLF(c, pos)
   else: result = pos
-  
-proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) = 
-  var pos = c.bufPos + 1          # skip "
-  var buf = c.buf                 # put `buf` in a register
+
+proc getString(c: var CfgParser, tok: var Token, rawMode: bool) =
+  var pos = c.bufpos + 1 # skip "
   tok.kind = tkSymbol
-  if (buf[pos] == '\"') and (buf[pos + 1] == '\"'): 
+  if (c.buf[pos] == '"') and (c.buf[pos + 1] == '"'):
     # long string literal:
-    inc(pos, 2)               # skip ""
+    inc(pos, 2) # skip ""
                               # skip leading newline:
-    pos = HandleCRLF(c, pos)
-    buf = c.buf
-    while true: 
-      case buf[pos]
-      of '\"': 
-        if (buf[pos + 1] == '\"') and (buf[pos + 2] == '\"'): break 
-        add(tok.literal, '\"')
-        Inc(pos)
-      of '\c', '\L': 
-        pos = HandleCRLF(c, pos)
-        buf = c.buf
+    pos = handleCRLF(c, pos)
+    while true:
+      case c.buf[pos]
+      of '"':
+        if (c.buf[pos + 1] == '"') and (c.buf[pos + 2] == '"'): break
+        add(tok.literal, '"')
+        inc(pos)
+      of '\c', '\L':
+        pos = handleCRLF(c, pos)
         add(tok.literal, "\n")
-      of lexbase.EndOfFile: 
+      of lexbase.EndOfFile:
         tok.kind = tkInvalid
-        break 
-      else: 
-        add(tok.literal, buf[pos])
-        Inc(pos)
-    c.bufpos = pos + 3       # skip the three """
-  else: 
+        break
+      else:
+        add(tok.literal, c.buf[pos])
+        inc(pos)
+    c.bufpos = pos + 3 # skip the three """
+  else:
     # ordinary string literal
-    while true: 
-      var ch = buf[pos]
-      if ch == '\"': 
-        inc(pos)              # skip '"'
-        break 
-      if ch in {'\c', '\L', lexbase.EndOfFile}: 
+    while true:
+      var ch = c.buf[pos]
+      if ch == '"':
+        inc(pos) # skip '"'
+        break
+      if ch in {'\c', '\L', lexbase.EndOfFile}:
         tok.kind = tkInvalid
-        break 
-      if (ch == '\\') and not rawMode: 
-        c.bufPos = pos
+        break
+      if (ch == '\\') and not rawMode:
+        c.bufpos = pos
         getEscapedChar(c, tok)
-        pos = c.bufPos
-      else: 
+        pos = c.bufpos
+      else:
         add(tok.literal, ch)
-        Inc(pos)
+        inc(pos)
     c.bufpos = pos
 
-proc getSymbol(c: var TCfgParser, tok: var TToken) = 
+proc getSymbol(c: var CfgParser, tok: var Token) =
   var pos = c.bufpos
-  var buf = c.buf
-  while true: 
-    add(tok.literal, buf[pos])
-    Inc(pos)
-    if not (buf[pos] in SymChars): break 
+  while true:
+    add(tok.literal, c.buf[pos])
+    inc(pos)
+    if not (c.buf[pos] in SymChars): break
+
+  while tok.literal.len > 0 and tok.literal[^1] == ' ':
+    tok.literal.setLen(tok.literal.len - 1)
+
   c.bufpos = pos
   tok.kind = tkSymbol
 
-proc skip(c: var TCfgParser) = 
+proc skip(c: var CfgParser) =
   var pos = c.bufpos
-  var buf = c.buf
-  while true: 
-    case buf[pos]
-    of ' ', '\t': 
-      Inc(pos)
-    of '#', ';': 
-      while not (buf[pos] in {'\c', '\L', lexbase.EndOfFile}): inc(pos)
-    of '\c', '\L': 
-      pos = HandleCRLF(c, pos)
-      buf = c.buf
-    else: 
-      break                   # EndOfFile also leaves the loop
+  while true:
+    case c.buf[pos]
+    of ' ', '\t':
+      inc(pos)
+    of '#', ';':
+      while not (c.buf[pos] in {'\c', '\L', lexbase.EndOfFile}): inc(pos)
+    of '\c', '\L':
+      pos = handleCRLF(c, pos)
+    else:
+      break # EndOfFile also leaves the loop
   c.bufpos = pos
 
-proc rawGetTok(c: var TCfgParser, tok: var TToken) = 
+proc rawGetTok(c: var CfgParser, tok: var Token) =
   tok.kind = tkInvalid
-  setlen(tok.literal, 0)
+  setLen(tok.literal, 0)
   skip(c)
   case c.buf[c.bufpos]
-  of '=': 
+  of '=':
     tok.kind = tkEquals
     inc(c.bufpos)
     tok.literal = "="
-  of '-': 
-    inc(c.bufPos)
-    if c.buf[c.bufPos] == '-': inc(c.bufPos)
-    tok.kind = tkDashDash
-    tok.literal = "--"
-  of ':': 
+  of '-':
+    inc(c.bufpos)
+    if c.buf[c.bufpos] == '-':
+      inc(c.bufpos)
+      tok.kind = tkDashDash
+      tok.literal = "--"
+    else:
+      dec(c.bufpos)
+      getSymbol(c, tok)
+  of ':':
     tok.kind = tkColon
     inc(c.bufpos)
     tok.literal = ":"
-  of 'r', 'R': 
-    if c.buf[c.bufPos + 1] == '\"': 
-      Inc(c.bufPos)
+  of 'r', 'R':
+    if c.buf[c.bufpos + 1] == '\"':
+      inc(c.bufpos)
       getString(c, tok, true)
-    else: 
+    else:
       getSymbol(c, tok)
-  of '[': 
+  of '[':
     tok.kind = tkBracketLe
     inc(c.bufpos)
-    tok.literal = "]"
-  of ']': 
+    tok.literal = "["
+  of ']':
     tok.kind = tkBracketRi
-    Inc(c.bufpos)
+    inc(c.bufpos)
     tok.literal = "]"
-  of '\"': 
+  of '"':
     getString(c, tok, false)
-  of lexbase.EndOfFile: 
+  of lexbase.EndOfFile:
     tok.kind = tkEof
     tok.literal = "[EOF]"
   else: getSymbol(c, tok)
-  
-proc errorStr(c: TCfgParser, msg: string): string = 
-  result = `%`("$1($2, $3) Error: $4", 
-               [c.filename, $getLine(c), $getColumn(c), msg])
-
-proc getKeyValPair(c: var TCfgParser, kind: TCfgEventKind): TCfgEvent = 
-  if c.tok.kind == tkSymbol: 
-    result.kind = kind
-    result.key = c.tok.literal
-    result.value = ""
+
+proc errorStr*(c: CfgParser, msg: string): string {.rtl, extern: "npc$1".} =
+  ## Returns a properly formatted error message containing current line and
+  ## column information.
+  result = `%`("$1($2, $3) Error: $4",
+                [c.filename, $getLine(c), $getColumn(c), msg])
+
+proc warningStr*(c: CfgParser, msg: string): string {.rtl, extern: "npc$1".} =
+  ## Returns a properly formatted warning message containing current line and
+  ## column information.
+  result = `%`("$1($2, $3) Warning: $4",
+                [c.filename, $getLine(c), $getColumn(c), msg])
+
+proc ignoreMsg*(c: CfgParser, e: CfgEvent): string {.rtl, extern: "npc$1".} =
+  ## Returns a properly formatted warning message containing that
+  ## an entry is ignored.
+  case e.kind
+  of cfgSectionStart: result = c.warningStr("section ignored: " & e.section)
+  of cfgKeyValuePair: result = c.warningStr("key ignored: " & e.key)
+  of cfgOption:
+    result = c.warningStr("command ignored: " & e.key & ": " & e.value)
+  of cfgError: result = e.msg
+  of cfgEof: result = ""
+
+proc getKeyValPair(c: var CfgParser, kind: CfgEventKind): CfgEvent =
+  if c.tok.kind == tkSymbol:
+    case kind
+    of cfgOption, cfgKeyValuePair:
+      result = CfgEvent(kind: kind, key: c.tok.literal.move, value: "")
+    else: discard
     rawGetTok(c, c.tok)
-    if c.tok.kind in {tkEquals, tkColon}: 
+    if c.tok.kind in {tkEquals, tkColon}:
       rawGetTok(c, c.tok)
-      if c.tok.kind == tkSymbol: 
+      if c.tok.kind == tkSymbol:
         result.value = c.tok.literal
-      else: 
-        result.kind = cfgError
-        result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal)
+      else:
+        result = CfgEvent(kind: cfgError,
+          msg: errorStr(c, "symbol expected, but found: " & c.tok.literal))
       rawGetTok(c, c.tok)
-  else: 
-    result.kind = cfgError
-    result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal)
+  else:
+    result = CfgEvent(kind: cfgError,
+      msg: errorStr(c, "symbol expected, but found: " & c.tok.literal))
     rawGetTok(c, c.tok)
 
-proc next(c: var TCfgParser): TCfgEvent = 
-  case c.tok.kind  
-  of tkEof: 
-    result.kind = cfgEof
-  of tkDashDash: 
+proc next*(c: var CfgParser): CfgEvent {.rtl, extern: "npc$1".} =
+  ## Retrieves the first/next event. This controls the parser.
+  case c.tok.kind
+  of tkEof:
+    result = CfgEvent(kind: cfgEof)
+  of tkDashDash:
     rawGetTok(c, c.tok)
     result = getKeyValPair(c, cfgOption)
-  of tkSymbol: 
+  of tkSymbol:
     result = getKeyValPair(c, cfgKeyValuePair)
-  of tkBracketLe: 
+  of tkBracketLe:
     rawGetTok(c, c.tok)
-    if c.tok.kind == tkSymbol: 
-      result.kind = cfgSectionStart
-      result.section = c.tok.literal
-    else: 
-      result.kind = cfgError
-      result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal)
+    if c.tok.kind == tkSymbol:
+      result = CfgEvent(kind: cfgSectionStart, section: c.tok.literal.move)
+    else:
+      result = CfgEvent(kind: cfgError,
+        msg: errorStr(c, "symbol expected, but found: " & c.tok.literal))
     rawGetTok(c, c.tok)
-    if c.tok.kind == tkBracketRi: 
+    if c.tok.kind == tkBracketRi:
       rawGetTok(c, c.tok)
-    else: 
-      result.kind = cfgError
-      result.msg = errorStr(c, "\']\' expected, but found: " & c.tok.literal)
-  of tkInvalid, tkEquals, tkColon, tkBracketRi: 
-    result.kind = cfgError
-    result.msg = errorStr(c, "invalid token: " & c.tok.literal)
+    else:
+      result = CfgEvent(kind: cfgError,
+        msg: errorStr(c, "']' expected, but found: " & c.tok.literal))
+  of tkInvalid, tkEquals, tkColon, tkBracketRi:
+    result = CfgEvent(kind: cfgError,
+      msg: errorStr(c, "invalid token: " & c.tok.literal))
     rawGetTok(c, c.tok)
+
+# ---------------- Configuration file related operations ----------------
+type
+  Config* = OrderedTableRef[string, OrderedTableRef[string, string]]
+
+proc newConfig*(): Config =
+  ## Creates a new configuration table.
+  ## Useful when wanting to create a configuration file.
+  result = newOrderedTable[string, OrderedTableRef[string, string]]()
+
+proc loadConfig*(stream: Stream, filename: string = "[stream]"): Config =
+  ## Loads the specified configuration from stream into a new Config instance.
+  ## `filename` parameter is only used for nicer error messages.
+  var dict = newOrderedTable[string, OrderedTableRef[string, string]]()
+  var curSection = "" ## Current section,
+                      ## the default value of the current section is "",
+                      ## which means that the current section is a common
+  var p: CfgParser
+  open(p, stream, filename)
+  while true:
+    var e = next(p)
+    case e.kind
+    of cfgEof:
+      break
+    of cfgSectionStart: # Only look for the first time the Section
+      curSection = e.section
+    of cfgKeyValuePair:
+      var t = newOrderedTable[string, string]()
+      if dict.hasKey(curSection):
+        t = dict[curSection]
+      t[e.key] = e.value
+      dict[curSection] = t
+    of cfgOption:
+      var c = newOrderedTable[string, string]()
+      if dict.hasKey(curSection):
+        c = dict[curSection]
+      c["--" & e.key] = e.value
+      dict[curSection] = c
+    of cfgError:
+      break
+  close(p)
+  result = dict
+
+proc loadConfig*(filename: string): Config =
+  ## Loads the specified configuration file into a new Config instance.
+  let file = open(filename, fmRead)
+  let fileStream = newFileStream(file)
+  defer: fileStream.close()
+  result = fileStream.loadConfig(filename)
+
+proc replace(s: string): string =
+  var d = ""
+  var i = 0
+  while i < s.len():
+    if s[i] == '\\':
+      d.add(r"\\")
+    elif s[i] == '\c' and s[i+1] == '\l':
+      d.add(r"\c\l")
+      inc(i)
+    elif s[i] == '\c':
+      d.add(r"\n")
+    elif s[i] == '\l':
+      d.add(r"\n")
+    else:
+      d.add(s[i])
+    inc(i)
+  result = d
+
+proc writeConfig*(dict: Config, stream: Stream) =
+  ## Writes the contents of the table to the specified stream.
+  ##
+  ## .. note:: Comment statement will be ignored.
+  for section, sectionData in dict.pairs():
+    if section != "": ## Not general section
+      if not allCharsInSet(section, SymChars): ## Non system character
+        stream.writeLine("[\"" & section & "\"]")
+      else:
+        stream.writeLine("[" & section & "]")
+    for key, value in sectionData.pairs():
+      var kv, segmentChar: string
+      if key.len > 1 and key[0] == '-' and key[1] == '-': ## If it is a command key
+        segmentChar = ":"
+        if not allCharsInSet(key[2..key.len()-1], SymChars):
+          kv.add("--\"")
+          kv.add(key[2..key.len()-1])
+          kv.add("\"")
+        else:
+          kv = key
+      else:
+        segmentChar = "="
+        kv = key
+      if value != "": ## If the key is not empty
+        if not allCharsInSet(value, SymChars):
+          if find(value, '"') == -1:
+            kv.add(segmentChar)
+            kv.add("\"")
+            kv.add(replace(value))
+            kv.add("\"")
+          else:
+            kv.add(segmentChar)
+            kv.add("\"\"\"")
+            kv.add(replace(value))
+            kv.add("\"\"\"")
+        else:
+          kv.add(segmentChar)
+          kv.add(value)
+      stream.writeLine(kv)
+
+proc `$`*(dict: Config): string =
+  ## Writes the contents of the table to string.
+  ## 
+  ## .. note:: Comment statement will be ignored.
+  let stream = newStringStream()
+  defer: stream.close()
+  dict.writeConfig(stream)
+  result = stream.data
+
+proc writeConfig*(dict: Config, filename: string) =
+  ## Writes the contents of the table to the specified configuration file.
+  ## 
+  ## .. note:: Comment statement will be ignored.
+  let file = open(filename, fmWrite)
+  defer: file.close()
+  let fileStream = newFileStream(file)
+  dict.writeConfig(fileStream)
+
+proc getSectionValue*(dict: Config, section, key: string, defaultVal = ""): string =
+  ## Gets the key value of the specified Section.
+  ## Returns the specified default value if the specified key does not exist.
+  if dict.hasKey(section):
+    if dict[section].hasKey(key):
+      result = dict[section][key]
+    else:
+      result = defaultVal
+  else:
+    result = defaultVal
+
+proc setSectionKey*(dict: var Config, section, key, value: string) =
+  ## Sets the Key value of the specified Section.
+  var t = newOrderedTable[string, string]()
+  if dict.hasKey(section):
+    t = dict[section]
+  t[key] = value
+  dict[section] = t
+
+proc delSection*(dict: var Config, section: string) =
+  ## Deletes the specified section and all of its sub keys.
+  dict.del(section)
+
+proc delSectionKey*(dict: var Config, section, key: string) =
+  ## Deletes the key of the specified section.
+  if dict.hasKey(section):
+    if dict[section].hasKey(key):
+      if dict[section].len == 1:
+        dict.del(section)
+      else:
+        dict[section].del(key)
+
+iterator sections*(dict: Config): lent string {.since: (1, 5).} =
+  ## Iterates through the sections in the `dict`.
+  for section in dict.keys:
+    yield section