#
#
# Nimrod's Runtime Library
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# String tables.
import
os, nhashes, strutils
type
TStringTableMode* = enum
modeCaseSensitive, # the table is case sensitive
modeCaseInsensitive, # the table is case insensitive
modeStyleInsensitive # the table is style insensitive
TKeyValuePair* = tuple[key, val: string]
TKeyValuePairSeq* = seq[TKeyValuePair]
TStringTable* = object of TObject
counter*: int
data*: TKeyValuePairSeq
mode*: TStringTableMode
PStringTable* = ref TStringTable
proc newStringTable*(keyValuePairs: openarray[string],
mode: TStringTableMode = modeCaseSensitive): PStringTable
proc put*(t: PStringTable, key, val: string)
proc get*(t: PStringTable, key: string): string
proc hasKey*(t: PStringTable, key: string): bool
proc length*(t: PStringTable): int
type
TFormatFlag* = enum
useEnvironment, # use environment variable if the ``$key``
# is not found in the table
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)
TFormatFlags* = set[TFormatFlag]
proc `%`*(f: string, t: PStringTable, flags: TFormatFlags = {}): string
# implementation
const
growthFactor = 2
startSize = 64
proc newStringTable(keyValuePairs: openarray[string],
mode: TStringTableMode = modeCaseSensitive): PStringTable =
new(result)
result.mode = mode
result.counter = 0
newSeq(result.data, startSize)
var i = 0
while i < high(keyValuePairs):
put(result, keyValuePairs[i], keyValuePairs[i + 1])
inc(i, 2)
proc myhash(t: PStringTable, key: string): THash =
case t.mode
of modeCaseSensitive: result = nhashes.GetHashStr(key)
of modeCaseInsensitive: result = nhashes.GetHashStrCI(key)
of modeStyleInsensitive: result = nhashes.getNormalizedHash(key)
proc myCmp(t: PStringTable, a, b: string): bool =
case t.mode
of modeCaseSensitive: result = cmp(a, b) == 0
of modeCaseInsensitive: result = cmpIgnoreCase(a, b) == 0
of modeStyleInsensitive: result = cmpIgnoreStyle(a, b) == 0
proc mustRehash(length, counter: int): bool =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc length(t: PStringTable): int =
result = t.counter
proc nextTry(h, maxHash: THash): THash =
result = ((5 * h) + 1) and maxHash
# For any initial h in range(maxHash), repeating that maxHash times
# generates each int in range(maxHash) exactly once (see any text on
# random-number generation for proof).
proc RawGet(t: PStringTable, key: string): int =
var h = myhash(t, key) and high(t.data) # start with real hash value
while not isNil(t.data[h].key):
if mycmp(t, t.data[h].key, key):
return h
h = nextTry(h, high(t.data))
result = - 1
proc get(t: PStringTable, key: string): string =
var index = RawGet(t, key)
if index >= 0: result = t.data[index].val
else: result = ""
proc hasKey(t: PStringTable, key: string): bool =
result = rawGet(t, key) >= 0
proc RawInsert(t: PStringTable, data: var TKeyValuePairSeq, key, val: string) =
var h = myhash(t, key) and high(data)
while not isNil(data[h].key):
h = nextTry(h, high(data))
data[h].key = key
data[h].val = val
proc Enlarge(t: PStringTable) =
var n: TKeyValuePairSeq
newSeq(n, len(t.data) * growthFactor)
for i in countup(0, high(t.data)):
if not isNil(t.data[i].key): RawInsert(t, n, t.data[i].key, t.data[i].val)
swap(t.data, n)
proc Put(t: PStringTable, key, val: string) =
var index = RawGet(t, key)
if index >= 0:
t.data[index].val = val
else:
if mustRehash(len(t.data), t.counter): Enlarge(t)
RawInsert(t, t.data, key, val)
inc(t.counter)
proc RaiseFormatException(s: string) =
var e: ref EInvalidValue
new(e)
e.msg = "format string: key not found: " & s
raise e
proc getValue(t: PStringTable, flags: TFormatFlags, key: string): string =
if hasKey(t, key): return get(t, key)
if useEnvironment in flags: result = os.getEnv(key)
else: result = ""
if result.len == 0:
if useKey in flags: result = '$' & key
elif not (useEmpty in flags): raiseFormatException(key)
proc `%`(f: string, t: PStringTable, flags: TFormatFlags = {}): string =
const
PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'}
result = ""
var i = 0
while i <= len(f) + 0 - 1:
if f[i] == '$':
case f[i + 1]
of '$':
add(result, '$')
inc(i, 2)
of '{':
var j = i + 1
while (j <= len(f) + 0 - 1) and (f[j] != '}'): inc(j)
var key = copy(f, i + 2 + 0 - 1, j - 1 + 0 - 1)
add(result, getValue(t, flags, key))
i = j + 1
of 'a'..'z', 'A'..'Z', '\x80'..'\xFF', '_':
var j = i + 1
while (j <= len(f) + 0 - 1) and (f[j] in PatternChars): inc(j)
var key = copy(f, i + 1 + 0 - 1, j - 1 + 0 - 1)
add(result, getValue(t, flags, key))
i = j
else:
add(result, f[i])
inc(i)
else:
add(result, f[i])
inc(i)