summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--doc/lib.rst3
-rw-r--r--lib/pure/strtabs.nim313
2 files changed, 210 insertions, 106 deletions
diff --git a/doc/lib.rst b/doc/lib.rst
index 6cb933f8a..a6d5e4e78 100644
--- a/doc/lib.rst
+++ b/doc/lib.rst
@@ -137,8 +137,7 @@ String handling
 * `strtabs <strtabs.html>`_
   The ``strtabs`` module implements an efficient hash table that is a mapping
   from strings to strings. Supports a case-sensitive, case-insensitive and
-  style-insensitive mode. An efficient string substitution operator ``%``
-  for the string table is also provided.
+  style-insensitive modes.
 
 * `unicode <unicode.html>`_
   This module provides support to handle the Unicode UTF-8 encoding.
diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim
index 377178f92..36e4e376f 100644
--- a/lib/pure/strtabs.nim
+++ b/lib/pure/strtabs.nim
@@ -9,11 +9,9 @@
 
 ## The ``strtabs`` module implements an efficient hash table that is a mapping
 ## from strings to strings. Supports a case-sensitive, case-insensitive and
-## style-insensitive mode. An efficient string substitution operator  ``%``
-## for the string table is also provided.
+## style-insensitive mode.
 
 runnableExamples:
-
   var t = newStringTable()
   t["name"] = "John"
   t["city"] = "Monaco"
@@ -22,17 +20,13 @@ runnableExamples:
   doAssert "name" in t
 
 ## String tables can be created from a table constructor:
-
 runnableExamples:
   var t = {"name": "John", "city": "Monaco"}.newStringTable
 
-
-## When using the style insensitive mode ``modeStyleInsensitive``, 
+## When using the style insensitive mode (``modeStyleInsensitive``),
 ## all letters are compared case insensitively within the ASCII range
 ## and underscores are ignored.
-
 runnableExamples:
-
   var x = newStringTable(modeStyleInsensitive)
   x["first_name"] = "John"
   x["LastName"] = "Doe"
@@ -40,6 +34,21 @@ runnableExamples:
   doAssert x["firstName"] == "John"
   doAssert x["last_name"] == "Doe"
 
+## An efficient string substitution operator
+## `% <#%25,string,StringTableRef,set[FormatFlag]>`_ for the string table
+## is also provided.
+runnableExamples:
+  var t = {"name": "John", "city": "Monaco"}.newStringTable
+  doAssert "${name} lives in ${city}" % t == "John lives in Monaco"
+
+## **See also:**
+## * `tables module <tables.html>`_ for general hash tables
+## * `sharedtables module<sharedtables.html>`_ for shared hash table support
+## * `strutils module<strutils.html>`_ for common string functions
+## * `json module<json.html>`_ for table-like structure which allows
+##   heterogeneous members
+
+
 import
   hashes, strutils
 
@@ -51,10 +60,10 @@ else:
   include "system/inclrtl"
 
 type
-  StringTableMode* = enum     ## describes the tables operation mode
-    modeCaseSensitive,        ## the table is case sensitive
-    modeCaseInsensitive,      ## the table is case insensitive
-    modeStyleInsensitive      ## the table is style insensitive
+  StringTableMode* = enum   ## Describes the tables operation mode.
+    modeCaseSensitive,      ## the table is case sensitive
+    modeCaseInsensitive,    ## the table is case insensitive
+    modeStyleInsensitive    ## the table is style insensitive
   KeyValuePair = tuple[key, val: string, hasValue: bool]
   KeyValuePairSeq = seq[KeyValuePair]
   StringTableObj* = object of RootObj
@@ -62,43 +71,41 @@ type
     data: KeyValuePairSeq
     mode: StringTableMode
 
-  StringTableRef* = ref StringTableObj ## use this type to declare string tables
+  StringTableRef* = ref StringTableObj
+
+  FormatFlag* = enum  ## Flags for the `%` operator.
+    useEnvironment,   ## Use environment variable if the ``$key``
+                      ## is not found in the table.
+                      ## Does nothing when using `js` target.
+    useEmpty,         ## Use the empty string as a default, thus it
+                      ## won't throw an exception if ``$key`` is not
+                      ## in the table.
+    useKey            ## Do not replace ``$key`` if it is not found
+                      ## in the table (or in the environment).
+
+const
+  growthFactor = 2
+  startSize = 64
 
-proc len*(t: StringTableRef): int {.rtlFunc, extern: "nst$1".} =
-  ## returns the number of keys in `t`.
-  result = t.counter
 
 iterator pairs*(t: StringTableRef): tuple[key, value: string] =
-  ## iterates over every (key, value) pair in the table `t`.
+  ## Iterates over every `(key, value)` pair in the table `t`.
   for h in 0..high(t.data):
     if t.data[h].hasValue:
       yield (t.data[h].key, t.data[h].val)
 
 iterator keys*(t: StringTableRef): string =
-  ## iterates over every key in the table `t`.
+  ## Iterates over every key in the table `t`.
   for h in 0..high(t.data):
     if t.data[h].hasValue:
       yield t.data[h].key
 
 iterator values*(t: StringTableRef): string =
-  ## iterates over every value in the table `t`.
+  ## Iterates over every value in the table `t`.
   for h in 0..high(t.data):
     if t.data[h].hasValue:
       yield t.data[h].val
 
-type
-  FormatFlag* = enum          ## flags for the `%` operator
-    useEnvironment,           ## use environment variable if the ``$key``
-                              ## is not found in the table. Does nothing when using `js` target.
-    useEmpty,                 ## use the empty string as a default, thus it
-                              ## won't throw an exception if ``$key`` is not
-                              ## in the table
-    useKey                    ## do not replace ``$key`` if it is not found
-                              ## in the table (or in the environment)
-
-const
-  growthFactor = 2
-  startSize = 64
 
 proc myhash(t: StringTableRef, key: string): Hash =
   case t.mode
@@ -136,24 +143,118 @@ template get(t: StringTableRef, key: string) =
     else:
       raise newException(KeyError, "key not found")
 
+proc `[]=`*(t: StringTableRef, key, val: string) {.
+  rtlFunc, extern: "nstPut", noSideEffect.}
+
+proc newStringTable*(mode: StringTableMode): StringTableRef {.
+  rtlFunc, extern: "nst$1".} =
+  ## Creates a new empty string table.
+  ##
+  ## See also:
+  ## * `newStringTable(keyValuePairs) proc
+  ##   <#newStringTable,varargs[tuple[string,string]],StringTableMode>`_
+  new(result)
+  result.mode = mode
+  result.counter = 0
+  newSeq(result.data, startSize)
+
+proc newStringTable*(keyValuePairs: varargs[string],
+                     mode: StringTableMode): StringTableRef {.
+  rtlFunc, extern: "nst$1WithPairs".} =
+  ## Creates a new string table with given `key, value` string pairs.
+  ##
+  ## `StringTableMode` must be specified.
+  runnableExamples:
+    var mytab = newStringTable("key1", "val1", "key2", "val2",
+                               modeCaseInsensitive)
+
+  result = newStringTable(mode)
+  var i = 0
+  while i < high(keyValuePairs):
+    result[keyValuePairs[i]] = keyValuePairs[i + 1]
+    inc(i, 2)
+
+proc newStringTable*(keyValuePairs: varargs[tuple[key, val: string]],
+                     mode: StringTableMode = modeCaseSensitive): StringTableRef {.
+  rtlFunc, extern: "nst$1WithTableConstr".} =
+  ## Creates a new string table with given `(key, value)` tuple pairs.
+  ##
+  ## The default mode is case sensitive.
+  runnableExamples:
+    var
+      mytab1 = newStringTable({"key1": "val1", "key2": "val2"}, modeCaseInsensitive)
+      mytab2 = newStringTable([("key3", "val3"), ("key4", "val4")])
+
+  result = newStringTable(mode)
+  for key, val in items(keyValuePairs): result[key] = val
+
+proc len*(t: StringTableRef): int {.rtlFunc, extern: "nst$1".} =
+  ## Returns the number of keys in `t`.
+  result = t.counter
+
 proc `[]`*(t: StringTableRef, key: string): var string {.
            rtlFunc, extern: "nstTake".} =
-  ## retrieves the location at ``t[key]``. If `key` is not in `t`, the
-  ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether
-  ## the key exists.
+  ## Retrieves the location at ``t[key]``.
+  ##
+  ## If `key` is not in `t`, the ``KeyError`` exception is raised.
+  ## One can check with `hasKey proc <#hasKey,StringTableRef,string>`_
+  ## whether the key exists.
+  ##
+  ## See also:
+  ## * `getOrDefault proc <#getOrDefault,StringTableRef,string,string>`_
+  ## * `[]= proc <#[]=,StringTableRef,string,string>`_ for inserting a new
+  ##   (key, value) pair in the table
+  ## * `hasKey proc <#hasKey,StringTableRef,string>`_ for checking if a key
+  ##   is in the table
+  runnableExamples:
+    var t = {"name": "John", "city": "Monaco"}.newStringTable
+    doAssert t["name"] == "John"
+    doAssertRaises(KeyError):
+      echo t["occupation"]
   get(t, key)
 
 proc getOrDefault*(t: StringTableRef; key: string, default: string = ""): string =
+  ## Retrieves the location at ``t[key]``.
+  ##
+  ## If `key` is not in `t`, the default value is returned (if not specified,
+  ## it is an empty string (`""`)).
+  ##
+  ## See also:
+  ## * `[] proc <#[],StringTableRef,string>`_ for retrieving a value of a key
+  ## * `hasKey proc <#hasKey,StringTableRef,string>`_ for checking if a key
+  ##   is in the table
+  ## * `[]= proc <#[]=,StringTableRef,string,string>`_ for inserting a new
+  ##   (key, value) pair in the table
+  runnableExamples:
+    var t = {"name": "John", "city": "Monaco"}.newStringTable
+    doAssert t.getOrDefault("name") == "John"
+    doAssert t.getOrDefault("occupation") == ""
+    doAssert t.getOrDefault("occupation", "teacher") == "teacher"
+    doAssert t.getOrDefault("name", "Paul") == "John"
+
   var index = rawGet(t, key)
   if index >= 0: result = t.data[index].val
   else: result = default
 
 proc hasKey*(t: StringTableRef, key: string): bool {.rtlFunc, extern: "nst$1".} =
-  ## returns true iff `key` is in the table `t`.
+  ## Returns true if `key` is in the table `t`.
+  ##
+  ## See also:
+  ## * `getOrDefault proc <#getOrDefault,StringTableRef,string,string>`_
+  ## * `contains proc <#contains,StringTableRef,string>`_
+  runnableExamples:
+    var t = {"name": "John", "city": "Monaco"}.newStringTable
+    doAssert t.hasKey("name")
+    doAssert not t.hasKey("occupation")
   result = rawGet(t, key) >= 0
 
 proc contains*(t: StringTableRef, key: string): bool =
-  ## alias of `hasKey` for use with the `in` operator.
+  ## Alias of `hasKey proc <#hasKey,StringTableRef,string>`_ for use with
+  ## the `in` operator.
+  runnableExamples:
+    var t = {"name": "John", "city": "Monaco"}.newStringTable
+    doAssert "name" in t
+    doAssert "occupation" notin t
   return hasKey(t, key)
 
 proc rawInsert(t: StringTableRef, data: var KeyValuePairSeq, key, val: string) =
@@ -171,8 +272,18 @@ proc enlarge(t: StringTableRef) =
     if t.data[i].hasValue: rawInsert(t, n, t.data[i].key, t.data[i].val)
   swap(t.data, n)
 
-proc `[]=`*(t: StringTableRef, key, val: string) {.rtlFunc, extern: "nstPut".} =
-  ## puts a (key, value)-pair into `t`.
+proc `[]=`*(t: StringTableRef, key, val: string) {.
+  rtlFunc, extern: "nstPut", noSideEffect.} =
+  ## Inserts a `(key, value)` pair into `t`.
+  ##
+  ## See also:
+  ## * `[] proc <#[],StringTableRef,string>`_ for retrieving a value of a key
+  ## * `del proc <#del,StringTableRef,string>`_ for removing a key from the table
+  runnableExamples:
+    var t = {"name": "John", "city": "Monaco"}.newStringTable
+    t["occupation"] = "teacher"
+    doAssert t.hasKey("occupation")
+
   var index = rawGet(t, key)
   if index >= 0:
     t.data[index].val = val
@@ -199,81 +310,39 @@ proc getValue(t: StringTableRef, flags: set[FormatFlag], key: string): string =
     if useKey in flags: result = '$' & key
     elif useEmpty notin flags: raiseFormatException(key)
 
-proc newStringTable*(mode: StringTableMode): StringTableRef {.
-  rtlFunc, extern: "nst$1".} =
-  ## creates a new string table that is empty.
-  new(result)
-  result.mode = mode
-  result.counter = 0
-  newSeq(result.data, startSize)
-
 proc clear*(s: StringTableRef, mode: StringTableMode) {.
   rtlFunc, extern: "nst$1".} =
-  ## resets a string table to be empty again.
+  ## Resets a string table to be empty again.
+  ##
+  ## See also:
+  ## * `del proc <#del,StringTableRef,string>`_ for removing a key from the table
+  runnableExamples:
+    var t = {"name": "John", "city": "Monaco"}.newStringTable
+    clear(t, modeCaseSensitive)
+    doAssert len(t) == 0
+    doAssert "name" notin t
+    doAssert "city" notin t
   s.mode = mode
   s.counter = 0
   s.data.setLen(startSize)
   for i in 0..<s.data.len:
     s.data[i].hasValue = false
 
-proc newStringTable*(keyValuePairs: varargs[string],
-                     mode: StringTableMode): StringTableRef {.
-  rtlFunc, extern: "nst$1WithPairs".} =
-  ## creates a new string table with given key value pairs.
-  ## Example::
-  ##   var mytab = newStringTable("key1", "val1", "key2", "val2",
-  ##                              modeCaseInsensitive)
-  result = newStringTable(mode)
-  var i = 0
-  while i < high(keyValuePairs):
-    result[keyValuePairs[i]] = keyValuePairs[i + 1]
-    inc(i, 2)
-
-proc newStringTable*(keyValuePairs: varargs[tuple[key, val: string]],
-                     mode: StringTableMode = modeCaseSensitive): StringTableRef {.
-  rtlFunc, extern: "nst$1WithTableConstr".} =
-  ## creates a new string table with given key value pairs.
-  ## Example::
-  ##   var mytab = newStringTable({"key1": "val1", "key2": "val2"},
-  ##                              modeCaseInsensitive)
-  result = newStringTable(mode)
-  for key, val in items(keyValuePairs): result[key] = val
-
-proc `%`*(f: string, t: StringTableRef, flags: set[FormatFlag] = {}): string {.
-  rtlFunc, extern: "nstFormat".} =
-  ## The `%` operator for string tables.
+proc del*(t: StringTableRef, key: string) =
+  ## Removes `key` from `t`.
+  ##
+  ## See also:
+  ## * `clear proc <#clear,StringTableRef,StringTableMode>`_ for reseting a
+  ##   table to be empty
+  ## * `[]= proc <#[]=,StringTableRef,string,string>`_ for inserting a new
+  ##   (key, value) pair in the table
   runnableExamples:
     var t = {"name": "John", "city": "Monaco"}.newStringTable
-    doAssert "${name} lives in ${city}" % t == "John lives in Monaco"
-  const
-    PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'}
-  result = ""
-  var i = 0
-  while i < len(f):
-    if f[i] == '$':
-      case f[i+1]
-      of '$':
-        add(result, '$')
-        inc(i, 2)
-      of '{':
-        var j = i + 1
-        while j < f.len and f[j] != '}': inc(j)
-        add(result, getValue(t, flags, substr(f, i+2, j-1)))
-        i = j + 1
-      of 'a'..'z', 'A'..'Z', '\x80'..'\xFF', '_':
-        var j = i + 1
-        while j < f.len and f[j] in PatternChars: inc(j)
-        add(result, getValue(t, flags, substr(f, i+1, j-1)))
-        i = j
-      else:
-        add(result, f[i])
-        inc(i)
-    else:
-      add(result, f[i])
-      inc(i)
+    t.del("name")
+    doAssert len(t) == 1
+    doAssert "name" notin t
+    doAssert "city" in t
 
-proc del*(t: StringTableRef, key: string) =
-  ## Removes `key` from `t`.
   # Impl adapted from `tableimpl.delImplIdx`
   var i = rawGet(t, key)
   let msk = high(t.data)
@@ -299,7 +368,8 @@ proc del*(t: StringTableRef, key: string) =
           shallowCopy(t.data[j], t.data[i]) # data[j] will be marked EMPTY next loop
 
 proc `$`*(t: StringTableRef): string {.rtlFunc, extern: "nstDollar".} =
-  ## The `$` operator for string tables.
+  ## The `$` operator for string tables. Used internally when calling
+  ## `echo` on a table.
   if t.len == 0:
     result = "{:}"
   else:
@@ -311,6 +381,41 @@ proc `$`*(t: StringTableRef): string {.rtlFunc, extern: "nstDollar".} =
       result.add(val)
     result.add("}")
 
+proc `%`*(f: string, t: StringTableRef, flags: set[FormatFlag] = {}): string {.
+  rtlFunc, extern: "nstFormat".} =
+  ## The `%` operator for string tables.
+  runnableExamples:
+    var t = {"name": "John", "city": "Monaco"}.newStringTable
+    doAssert "${name} lives in ${city}" % t == "John lives in Monaco"
+
+  const
+    PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'}
+  result = ""
+  var i = 0
+  while i < len(f):
+    if f[i] == '$':
+      case f[i+1]
+      of '$':
+        add(result, '$')
+        inc(i, 2)
+      of '{':
+        var j = i + 1
+        while j < f.len and f[j] != '}': inc(j)
+        add(result, getValue(t, flags, substr(f, i+2, j-1)))
+        i = j + 1
+      of 'a'..'z', 'A'..'Z', '\x80'..'\xFF', '_':
+        var j = i + 1
+        while j < f.len and f[j] in PatternChars: inc(j)
+        add(result, getValue(t, flags, substr(f, i+1, j-1)))
+        i = j
+      else:
+        add(result, f[i])
+        inc(i)
+    else:
+      add(result, f[i])
+      inc(i)
+
+
 when isMainModule:
   var x = {"k": "v", "11": "22", "565": "67"}.newStringTable
   assert x["k"] == "v"