Stdin + Dictionary Text Replacement Tool -- Debugging

Viewed 278

I'm working on a project in which I have two main files. Essentially, the program reads in a text file defining a dictionary with key-value mappings. Each key has a unique value and the file is formatted like this where each key-value pair is on its own line:

ipsum i%#@!
fubar fubar
IpSum XXXXX24
Ipsum YYYYY211

Then the program reads in input from stdin, and if any of the "words" match the keys in the dictionary file, they get replaced with the value. There is a slight thing about upper and lower cases -- this is the order of "match priority"

  1. The exact word is in the replacement set
  2. The word with all but the first character converted to lower case is in the replacement set
  3. The word converted completely to lower case is in the replacement set

Meaning if the exact word is in the dictionary, it gets replaced, but if not the next possibility (2) is checked and so on...

My program passes the basic cases we were provided but then the terminal shows that the output vs reference binary files differ.

I went into both files (not c files, but binary files), and one was super long with tons of numbers and the other just had a line of random characters. So that didn't really help. I also reviewed my code and made some small tests but it seems okay? A friend recommended I make sure I'm accounting for the null operator in processInput() and I already was (or at least I think so, correct me if I'm wrong). I also converted getchar() to an int to properly check for EOF, and allocated extra space for the char array. I also tried vimdiff and got more confused. I would love some help debugging this, please! I've been at it all day and I'm very confused.

2 Answers

Start by testing your tokeniser with:

$ ./a.out <badhash2.c >zooi
$ diff badhash2.c zooi
$

Does it work for binary files, too?:

$ ./a.out <./a.out > zooibin
$ diff ./a.out zooibin
$

Yes, it does!


#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

void processInput(void);

int main(int argc, char **argv) {
  processInput();
  return 0;
}

void processInput() {
  int ch;
  char *word;
  int len = 0;
  int cap = 60;
  word = malloc(cap);

  while(1) {
    ch = getchar();                 // (1)
    if( ch != EOF && isalnum(ch)) { // (2)
        if(len+1 >= cap) {          // (3)
            cap += cap/2;
            word = realloc(word, cap);
        }
        word[len++] = ch;
    } else {
        if (len) {                  // (4)
#if 0
            char key[len + 1]; 
            memcpy(key, word, len); key[len] = 0; 
            checkData(key, len); 
#else
            word[len] = 0;
            fputs(word, stdout); 
#endif
            len = 0; 
            }
        if (ch == EOF) break;      // (5)
        putchar(ch); 
      }
    }
    free(word);

  }
                         

I only repaired your tokeniser, leaving out the hash table and the search & replace stuff. It is now supposed to generate a verbatim copy of the input. (which is silly, but great for testing)

  1. If you want to allow binary input, you cannot use while((ch = getchar()) ...) : a NUL in the input would cause the loop to end. You must pospone testing for EOF, because ther could still be a final word in your buffer ...&& ch != EOF)

  2. treat EOF just like a space here: it could be the end of a word

  3. you must reserve space for the NUL ('\0') , too.

  4. if (len==0) there would be no word, so no need to look it up.

  5. we treated EOF just like a space, but we don't want to write it to the output. Time to break out of the loop.

There are multiple issues in the processInput() function:

  • the loop should not stop when the byte read is 0, you should process the full input with:

      while ((ch = getchar()) != EOF)
    
  • the test for EOF should actually be done differently so the last word of the file gets a chance to be handled if it occurs exactly at the end of the file.

  • the cast in isalnum((char)ch) is incorrect: you should pass ch directly to isalnum. Casting as char is actually counterproductive because it will turn byte values beyond CHAR_MAX to negative values for which isalnum() has undefined behavior.

  • the test if(ind >= cap) is too loose: if word contains cap characters, setting the null terminator at word[ind] will write beyond the end of the array. Change the test to if (cap - ind < 2) to allow for a byte and a null terminator at all times.

  • you should check that there is at least one character in the word to avoid calling checkData() with an empty string.

  • char key[ind + 1]; is useless: you can just pass word to checkData().

  • checkData(key, ind) is incorrect: you should pass the size of the buffer for the case conversions, which is at least ind + 1 to allow for the null terminator.

  • the cast in putchar((char)ch); is useless and confusing.

There are some small issues in the rest of the code, but none that should cause a problem.

Related