Alex Nguyen
Alex Nguyen

Reputation: 11

How to read in specific data from a text file (C)?

I'm somewhat new to C (and also to this website) and had a question about a project I'm working on. The part I'm stuck on is file I/O stuff, since I never learned that part in my previous class. So, the input (.txt) file looks like this:

REGISTERS
R5 11
R7 15
R21 4
MEMORY
4 12
12 92
[OTHER STUFF]

The file gives us arbitrary register numbers, followed by its contents (R0 to 31, Regs with value 0 omitted). The next section gives memory locations in a similar format, followed by the contents again. Then more sections that I can deal with later.

My question is: how do I read in the register/memory locations and their values? Currently I have a size 32 array to store the contents in and a variable for the Reg number and value (Example: Registers[regNumber] = regValue;), but I don't know how to start properly reading values after the word REGISTERS.

What I've tried:

I did some looking around and I managed to read in a test input file with only one line: "R5 11". For this I did:

fscanf(file_ptr, "R%d %d", &regNumber, &regValue);

I printed the variables regNumber, regValue, and they properly gave me 5 and 11. But how do I do this for any number of lines after the word REGISTERS and stop at the word MEMORY?

I'm assuming whatever the solution is, I can do the same thing to read in the MEMORY locations and values.

Upvotes: 0

Views: 518

Answers (2)

Pablo
Pablo

Reputation: 13580

I wouldn't use fscanf here, because you cannot look ahead and see if the next line is REGISTERS or MEMORY or whatever. In theory you could check the return value of fscanf but the code would get very messy very quickly.

A better solution is to use fgets to fetch a whole line. If the line is equal to REGISTERS\n, then you know the next lines are register lines, etc. The parsing itself can be done with sscanf, which is like fscanf but instead of reading from a FILE* buffer, it reads from a string.

So, I'd do:

enum mode_t {
    REGISTERS = 0,
    MEMORY,
    ...,
    INVALID
};

int parse_file(FILE *file_ptr, int *registers, int some_other_params)
{
    if(file_ptr == NULL || registers == NULL)
        return 0;

    char line[1024];

    enum mode_t mode = INVALID;

    while(fgets(line, sizeof line, file_ptr))
    {
        if(strcmp(line, "REGISTERS\n") == 0)
        {
            mode = REGISTERS;
            continue;
        } else if(strcmp(line, "MEMORY\n") == 0) {
            mode = MEMORY;
            continue;
        } // keep adding if(strcmp.... as needed)

        if(mode == INVALID)
        {
            fprintf(stderr, "Invalid file format\n");
            return 0; // or a better error handling
        }

        int ret;
        switch(mode)
        {
            case REGISTERS:
                ret = parse_registers(line, registers);
                break;
            case MEMORY:
                ret = parse_memory(line, some_other_args);
                break;
            ...
        }

        // evaluate ret and do error handling if necessary
    }

    return 1;
}

Using mode for the parsing state, you can determine which section you are parsing. Then you have to write functions like parse_registers and parse_memory that do the real parsing of the lines, for example:

int parse_registers(const char *line, int *registers)
{
    if(line == NULL || registers == NULL)
        return 0;

    int regNumber, regValue;

    if(sscanf("R%d %d", &regNumber, &regValue) != 2)
    {
        fprintf(stderr, "Invalid format for REGISTER section\n");
        return 0;
    }

    if(regNumber < 0 || regNumber > 31)
    {
        fprintf(stderr, "Invalid register number %d\n", regNumber);
        return 0;
    }

    registers[regNumber] = regValue;

    return 1;
}

Now you can implement parse_memory and the other parse_xxx functions for the other sections of the file.

Upvotes: 2

Bwebb
Bwebb

Reputation: 685

You can read in a full line of the text file with fgets(), then strcmp or strncmp the line for the "REGISTERS" and "MEMORY" string literals.

The way your input.txt is formatted everything after "REGISTERS" has the same format up until "MEMORY", and everything after "MEMORY" is the same format. Maybe something like the following would work, although there are probably better alternatives:

int main () {
   FILE *fp;
   char str[60];
   int input_type = 0; //you can enum this for the input types you have
   int line_num = 0;

   /* opening file for reading */
   fp = fopen("input.txt" , "r");
   if(fp == NULL) {
      perror("Error opening file");
      return(-1);
   }
   while( fgets (str, 60, fp)!=NULL ) { // dont really need the != NULL compare here.
      line_num++;
      /* Parse the next line in the file */
      if(strcmp(str,"MEMORY") == 0){
         //the next lines will be formatted like memory input lines
         input_type = 2; //This should be an enum or macro
         continue;
      }
      if(strcmp(str,"REGISTERS") == 0){
         //the next lines will be formatted like register input lines
         input_type = 1;//Also should be enum or macro
         continue;
      }
      switch(input_type){
         case 0:
            fprintf(stderr,"Failed to find REGISTER keyword%d\n",line_num);
            break;
         case 1:
            sscanf(str, "R%d %d", &regNumber, &regValue); //parse known string format
            do_something(&regNumber, &regValue);
            break;
         case 2:
            sscanf(str, "%d %d", &memNumber, &memValue); //parse known string format
            do_something_else(&memNumber, &memValue);
            break;
         default:
            fprintf(stderr,"Could not find input format at line %d\n",line_num);
            break;
      }
   }
   fclose(fp);

   return(0);
}

Upvotes: 2

Related Questions