Command line program

Hello,

Here is the task:

Modify the program detab (written as exercises in Chapter 1) to accept a list of tab stops as arguments. Use the default tab settings if there are no arguments.

So tab stops are the next place where the cursor stops. When I hit a tab, I get to the next tab stop. Assuming tab stops at all multiples of 8 (8, 16, 24, 32, 40) etc If I'm in column 10 and hit a tab stop go to column 16...

So my question is, how should I modify it? Any help appreciated, here is my detab.c:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

#include <stdio.h>
#define MAXLINE 1000
#define SPACE  ' '
#define TAB  '\t'

/* Write a program detab that replaces tabs in the input
with the proper number of blanks to space to the next tab stop. 
Assume a fixed set of tab stops, say every n columns. Should n be
a variable or symbolic constant? */

int countspaces(int offset, int tabsize)
{

  return tabsize - (offset % tabsize);
   
}


int main(void)

{
  
  int pos, c;

  while((c = getchar()) != EOF)
  {
    
    
  
  if(c == TAB) 
        {
            int numSpaces = countspaces(pos, 5);   
            for(int i = 0; i < numSpaces; ++i)
            {
                putchar(SPACE);
                ++pos;
            }
        }
        
    else if( c == '\n')
        {
         
          putchar(c);
          pos = 0;
          
        }
        
    else
      {
         putchar(c);
         ++pos;
      }
   }
  
}

  

With "accept a list of tabstops" I assume you mean as command line argument? If so modify your program entry point to: int main(int argc, char* argv[]). That way you can accept command line arguments.

First thing to do would be to store the tabstops somewhere. I think a vector might be the easiest solution in this case: std::vector<int> tabstops;.

Next we parse the command line arguments to store the tabstop locations somewhere:

1
2
3
4
5
6
7
8
9
10
11
12
for(int i = 1; i < argc; ++i) //Loop through the command-line arguments
{
    //Convert the char* argument to an integer
    int location;
    std::stringstream ss;
    ss << argv[i];
    ss >> location;
    if(!ss.fail()) //Check if converting to integer succeeded
    {
        tabstops.push_back(location); //Append the integer to the vector
    }
}


Now you have a vector with all tabstop locations your user entered. The last thing to do will be to modify your countspaces function to fetch the new tabstop location from the vector:

1
2
3
4
5
6
7
8
9
int countspaces(const std::vector<int>& tabstops, int offset)
{
    std::vector<int>::iterator pos = std::upper_bound(tabstops.begin(),
            tabstops.end(), offset); //Find the nearest tabstop greater than this one
    int tabstop = DEFAULT_TAB; //Set this to your default tab width
    if(pos != tabstops.end()) //Check if there's a tabstop greater than this one
        tabstop = *pos; //Set it to the found tabstop
    return tabstop - (offset % tabstop); //Return the number of spaces
}


Then call your new function as: int numSpaces = countspaces(tabstops, pos);. I hope this helps.

Note that I didn't test this code yet, I think this should work though.

EDIT:

Note that you should not forget to initialize pos to 0 in your code. Your current code operates on an uninitialized variable pos until the first newline is entered.
Last edited on
Thank you Shadowwolf. The only problem is that I should do this in C...
Anyway this is what I came up regarding the main routine, does it look correct?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

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

#define MAXLINE 1000

int array[MAXLINE];          /* Fixed sized array ... Hmmmmm, should this be dynamic? */


int main (int argv, char *argv[]);

{
	
	int i, location;
	
	for (i = 1; i < argc; ++i)
	{
		location = atoi(&argv[i]);
		
	  if (i > 0 && i <= MAXLINE)
		array[location++];
		
	}
	
}

> array[location++];
statement has no effect.
Shouldn't it be array[pos++] = location; ?

Or is there a function in C like push_back in C++?
Last edited on
¿what's `pos'?
1
2
for(int K=1; K<argc and K<MAXLINE; ++K)
   array[K] = atoi(argv[K]);



> does it look correct?
it doesn't even compile.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 1000

int array[MAXLINE];          


int main (int argc, char *argv[])

{
	
    int i;
    for (i = 1; i < argc && i < MAXLINE; ++i)
	{
		array[i] = atoi(argv[i]);
	}
	
}




So now I have an array filled with the tab stops the user entered, but what next?
It's somewhat hard to read Shadowwolf's source, that is C++ not C. what does upper_bound do? If I knew it I might have a chance.
Last edited on
Shadowwolf was kind enough to answer that in a comment in his code.
You may also RTFM.

If you don't understand it, ask an especific question.
The general idea is to parse the numbers and store them somewhere, the code I gave was just an example (written in C++, it's not that hard to convert it to C). For example, if you would do the same with a dynamic array in C, you would probably do something like this:

1
2
3
4
5
6
7
8
9
10
11
//First we allocate memory for the command line parameters
size_t numtabs = (argc-1);
int* tabstops = (int*)malloc(numtabs * sizeof(int));
//Next we loop through all arguments
for(int i = 1; i < argc; ++i)
{
    //Convert the char* argument to an integer
    int location = atoi(argv[i]);
    //Then we store it in the array
    tabstops[i - 1] = location;
}


And your function would become something like this:

1
2
3
4
5
6
7
8
9
10
11
12
//We'll need an arraysize parameter since C arrays won't store this for us
int countspaces(int* tabstops, size_t arraysize, int offset)
{
    //Get the upper bound
    int* pos = get_upper_bound(tabstops,tabstops+arraysize,offset);
    //Set the default tab width
    int tabstop = DEFAULT_TAB;
    //Check if there was a position in the array
    if(pos != NULL)
        tabstop = *pos; //Set it to the found tabstop
    return tabstop - (offset % tabstop); //Return the number of spaces
}


Since C doesn't come with any sort of get_upper_bound function, we'll have to write it ourselves. Luckily this site comes with a reference which shows us what it does (and even an implementation of the code, but since we like to reinvent the wheel, let's do it ourselves).

std::upper_bound (located in the <algorithm> header) is a function that retrieves the first element in a container (or actually, an iterator range) is greater than the value specified. While the C++ standard library comes with a much better implementation (guaranteed to be log2(N)+1), ours will be linear, since that's just a simple loop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Returns the first value greater than val, between first (inclusive) and last (exclusive)
int* get_upper_bound(int* first, int* last, int val)
{
    while(first != last)
    {
        //Check if this value is greater than the specified one
        if(*first > val)
            return first; //If so, we found our answer
       //Otherwise we'll go to the next element
       ++first;
    }
    //If we make it here we couldn't find the value, so return an error
    //We'll use NULL here, although the C++ version returns last
    return NULL;
}


Of course, this code can be heavily optimized (see http://www.cplusplus.com/reference/algorithm/upper_bound/ for an idea). And this code isn't completely compatible with what the C++ standard says about upper_bound, for example we use the > operator while the standard says we should use the < operator (of course you could change the comparison to !(val < *first) and you've solved that problem). But since we're not implementing the C++ standard library here and only using int, I say we can get away with using this.

And now you can call this new function (written in nothing more than plain C, actually if you write atoi and malloc yourself you don't even have to rely on the C standard library) like this:

 
int numSpaces = countspaces(tabstops,numtabs,pos);


It's actually been a while since I last relied on pure C to do everything. Are your programming for some sort of embedded system or so?
Last edited on
Not actually. I'm doing just the exercises.

Now, how the main routine of my program would look like?

Also, I guess you meant int numSpaces = countspaces(tabstops,numtabs,location);
No it wasn't location I meant instead of pos. In your original code (your first post this thread) you had a variable pos that represented the current position in the line. You need to pass that one to the function. Location was just a variable in the loop. The complete code would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdlib.h>

//Returns the first value greater than val, between first (inclusive) and last (exclusive)
int* get_upper_bound(int* first, int* last, int val)
{
    while(first != last)
    {
        //Check if this value is greater than the specified one
        if(*first > val)
            return first; //If so, we found our answer
       //Otherwise we'll go to the next element
       ++first;
    }
    //If we make it here we couldn't find the value, so return an error
    //We'll use NULL here, although the C++ version returns last
    return NULL;
}

//We'll need an arraysize parameter since C arrays won't store this for us
int countspaces(int* tabstops, size_t arraysize, int offset)
{
    //Get the upper bound
    int* pos = get_upper_bound(tabstops,tabstops+arraysize,offset);
    //Set the default tab width
    int tabstop = DEFAULT_TAB;
    //Check if there was a position in the array
    if(pos != NULL)
        tabstop = *pos; //Set it to the found tabstop
    return tabstop - (offset % tabstop); //Return the number of spaces
}

int main(int argc, char* argv[])
{
    int pos =0, c = 0;
    //First we allocate memory for the command line parameters
    size_t numtabs = (argc-1);
    int* tabstops = (int*)malloc(numtabs * sizeof(int));
    //Next we loop through all arguments
    for(int i = 1; i < argc; ++i)
    {
        //Convert the char* argument to an integer
        int location = atoi(argv[i]);
        //Then we store it in the array
        tabstops[i - 1] = location;
    }
    while((c = getchar()) != EOF)
    {
        if(c == '\t') 
        {
            int numSpaces = countspaces(tabstops,numtabs,pos);
            for(int i = 0; i < numSpaces; ++i)
            {
                putchar(' ');
                ++pos;
            }
        }
        else if(c == '\n')
        {
            putchar(c);
            pos = 0;
        }   
        else
        {
            putchar(c);
            ++pos;
        }
    }
    return 0;
}
Last edited on
Hi, I guess this program expects a list of tab stops like this, some results with Clang: (Where the spaces represent a tab)


./detab 4 8 12 32 66
12	12345	12345678910	1234	


I don't think this is working as expected, do you get the same results?
Last edited on
The program expects a list of tabs as command line arguments.

I changed one more thing (a little mistake I made with the default tab width).
Instead of this in the numspaces function:
 
int tabstop = DEFAULT_TAB;


It should have been this:

 
int tabstop = DEFAULT_TAB * offset + (offset % DEFAULT_TAB); //Set the default tab position 


I made a little logic error there. But that shouldn't influence your program unless you forget to specify the spaces.

For me this does seem to work.


./detab 4 8 12 32 66
12      12345   1234567890      1234
12  12345   1234567890          1234


The first line shows what I entered in the console (spaces are tabs), the second line is the program output (all tabs replaced by spaces).
Last edited on
Topic archived. No new replies allowed.