summary refs log blame commit diff stats
path: root/rod/nstrtabs.nim
blob: 811e461ccd04e4cd3f19d90eb4c406cf4d3dafda (plain) (tree)
1
2
3
4


                                     
                                         

















































                                                                                



                                
           






















                                                                  
                                         



                                                                      

                                                 
                                                                      






                                                 
                            






                                                                                
                                       












                                                                              
                            













                                                                           
                                       

                                                     
                      





                                                                         
             
           






                             
                     
                                                             
                                                       


                                                  
                     
                                                                      
                                                       








                                            
#
#
#            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)