diff options
-rw-r--r-- | doc/lib.rst | 3 | ||||
-rw-r--r-- | lib/pure/strtabs.nim | 313 |
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" |