Tuesday, August 02, 2016

Dictionary lookup - bug fixes

One of our eagle-eyed forum members noticed a bug in our dictionary code (thanks to tsh!).  In a couple of places the code that dealt with keys was not looking for the end of the key name.  This caused problems when two keys started the same.  For example let's say we have a key "name" and another key "names of next of kin".  The second key starts with "name", and the code wasn't written to avoid a false detection of this sort.

The solution to this problem is to fully qualify the key names by adding the "~value~" tag into the detection code so that when we look for "name" we are actually looking for a match on "~key~name~value~" and not "~key~name", which would give us a false positive if we looking at any key that happened to start with "name"

Here is the fixed version of our dictionary inspector.  Enjoy!

    nomainwin
    WindowWidth = 555
    WindowHeight = 438
    dim keys$(1000)
    dim info$(10, 10)
    global dictionary$, keyCount, lastKey$
    call readDictionary

    texteditor #main.value, 175, 7, 360, 365
    listbox #main.keys, keys$(), [keySelected], 5, 7, 160, 365
    menu #main, "Key", "New", [newKey], "Delete", [deleteKey]
    menu #main, "Edit"
    open "Dictionary inspector" for window as #main
    #main "trapclose [quit]"
    #main.keys "singleclickselect"
    #main.value "!autoresize";
    call loadKeys

[main.inputLoop]   'wait here for input event
    wait

[newKey]   'ask the user for a new key
    call saveValue
    prompt "Enter a name for the key."; newKey$
    if newKey$ <> "" then
        call setValueByName newKey$, ""
        call loadKeys
        #main.keys "select "; newKey$
        #main.value "!cls";
        #main.value "!setfocus";
        call collectGarbage
        call writeDictionary
        lastKey$ = newKey$
    end if
    wait

[deleteKey]  'left for later
    notice "Delete not implemented yet."
    wait

[keySelected] 'a key in the list was selected
    call saveValue
    #main.keys "selection? selectedKey$"
    selectedValue$ = getValue$(selectedKey$)
    #main.value "!contents selectedValue$";
    lastKey$ = selectedKey$
    wait

[quit] 'End the program
    call saveValue
    close #main
    end

sub saveValue   'if the value is changed, save it
    if lastKey$ <> "" then
        #main.value "!modified? modified$";
        if modified$ = "true" then
            #main.value "!contents? saveThisValue$";
            call setValueByName lastKey$, saveThisValue$
            call collectGarbage
            call writeDictionary
        end if
    end if
end sub

function getKeys$(delimiter$)
  global keyCount
  pointer = 1
  while pointer <> 0
    'get the next key
    pointer = instr(dictionary$, "~key~", pointer)
    if pointer then
      keyPointer = pointer + 5
      pointer = instr(dictionary$, "~value~", pointer)
      key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
      if instr(keyList$, "~key~" + key$ + "~value~") = 0 then
        getKeys$ = getKeys$ + key$ + delimiter$
        keyList$ = keyList$ + "~key~" + key$
        keyCount = keyCount + 1
      end if
    end if
  wend
end function

sub writeDictionary
  open "dictionary.dat" for output as #writeDict
    print #writeDict, dictionary$
  close #writeDict
end sub

sub readDictionary
  if fileExists(DefaultDir$, "dictionary.dat") then
      open "dictionary.dat" for input as #readDict
    length = lof(#readDict)
    dictionary$ = input$(#readDict, length)
    close #readDict
  end if
end sub

sub collectGarbage
  pointer = 1
  while pointer > 0
    'get the next key
    pointer = instr(dictionary$, "~key~", pointer)
    if pointer then
      keyPointer = pointer + 5
      pointer = instr(dictionary$, "~value~", pointer)
      key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
      if instr(keyList$, "~key~" + key$ + "~value~") = 0 then
        value$ = getValue$(key$)
        newDictionary$ = "~key~" + key$ + "~value~" + value$ + newDictionary$
        keyList$ = keyList$ + "~key~" + key$ + "~value~"
      end if
    end if
  wend
  dictionary$ = newDictionary$
end sub

sub setValueByName key$, value$
  dictionary$ = "~key~"+key$+"~value~"+value$+dictionary$
end sub

function getValue$(key$)
  getValue$ = chr$(0)
  keyPosition = instr(dictionary$, "~key~"+key$+"~value~")
  if keyPosition > 0 then
    keyPosition = keyPosition + 5  'skip over key tag
    valuePosition = instr(dictionary$, "~value~",  keyPosition)
    if valuePosition > 0 then
      valuePosition = valuePosition + 7   'skip over value tag
      endPosition = instr(dictionary$, "~key~", valuePosition)
      if endPosition > 0 then
        getValue$ = mid$(dictionary$, valuePosition, endPosition - valuePosition)
      else
        getValue$ = mid$(dictionary$, valuePosition)
      end if
    end if
  end if
end function

sub loadKeys
    keyList$ = getKeys$("~")
    redim keys$(keyCount)
    for item = 1 to keyCount
      keys$(item-1) = word$(keyList$, item, "~")
    next item
    #main.keys "reload"
end sub

function fileExists(path$, filename$) ' Does file exist?
    files path$, filename$, info$(
    fileExists = val(info$(0, 0)) > 0
end function


No comments: