Exercise 4-10 (getcalc - Calculator with getline)

Chapter_4     Exercises_4-8,9 files     Exercise_4-11







Exercise 4-10     K&R, p. 79


Exercise 4-10. An alternate organization uses getline() to read an entire input line; this makes getch() and ungetch() unnecessary. Revise the calculator to use this approach.




getcalc.c         download


#include <stdio.h> // for printf(), getchar(), EOF
#include <stdlib.h> // for atof()

#define MAXLINE 1000 // max line length
#define MAXOP 100 // max size of operand or operator
#define NUMBER '0' // signal that a number has been found

int getLine(char [], int); // getline() is declared in stdio.h
char line[MAXLINE];
int lp; // line pointer

int getop(char []); // get the next operand or operator
void push(double); // push operand on stack
double pop(void); // pop operand from stack

int main()
{
int type, i1, i2, len;
double op2; // second operand for -, /, %
char s[MAXOP]; // operand or operator

while((len = getLine(line, MAXLINE)) > 0)
{ // nonempty line ending with EOF will not be executed
lp = 0; // reset

while(lp < len)
{
type = getop(s);
switch(type)
{
case NUMBER :
push(atof(s));
break;
case '+' :
push(pop() + pop());
break;
case '-' :
op2 = pop();
push(pop() - op2);
break;
case '*' :
push(pop() * pop());
break;
case '/' :
op2 = pop();
if (op2 != 0.0)
{push(pop() / op2);}
else {printf("Error: zero divisor\n");}
break;
case '%' :
i2 = pop(); // automatic conversions
i1 = pop(); // from double to int
if (i2 != 0.0)
{push(i1 % i2);} // auto conversion from int to double, arg of push()
else {printf("Error: zero divisor\n");}
break;
case '\n' :
printf("\t%.8g\n", pop()); // top of stack
break;
default :
printf("Unknown command: %s\n", s);
break;
} // end of switch()
// if (type == '\n') {break;} // out of inner while()
} // end of inner while()
} // end of outer while()

return 0;
} // end of main()

// getLine(): read a line into s[], return length
int getLine(char s[], int lim)
{
int c = EOF; // initialize
int i;
// getchar() is only executed if (i < (lim-1)):
for (i = 0; i < (lim-1) && (c = getchar()) != EOF && c != '\n'; i++)
{ // from 0 to lim-2; s[lim-2]='\n' or not, s[lim-1]='\0'
s[i] = c;
}
if (c == '\n') // i < (lim-1), getchar() executed
{
s[i] = c; // '\n'
i++;
}
s[i] = '\0'; // the null character ends a string

return i; // max(i) == (lim-1), assuming (lim-1) > 0
}

#define MAXVAL 100 // stack size
// stack pointer (0 is top of stack):
int sp = 0; // next free stack position
double val[MAXVAL]; // value stack

void push(double f) // push f on (top of) stack
{
if (sp < MAXVAL) {val[sp++] = f;}
else {printf("Error: stack full, can't push %g\n", f);}
}

double pop(void) // pop and return top value from stack
{
if (sp > 0) {return val[--sp];}
else
{
printf("Error: stack empty\n");
return 0.0;
}
}

#include <ctype.h> // for isdigit()

int getop(char s[]) // get next operator or numeric operand
{
int c, i;

while ((s[0] = c = line[lp++]) == ' ' || c == '\t') // skip beginning ' ', '\t'
{} // last value read in s[0] is not ' ' or '\t', but could be '\n' or EOF
s[1] = '\0'; // end string

if (!isdigit(c) && c != '.' && c != '+' && c != '-')
{return c;} // not a number, probably an operator (or '\n')
// here c is digit or '.' or '+' or '-'

i = 0;

if (c == '+' || c == '-') // unary or binary sign
{
c = line[lp++];
if (!isdigit(c) && c != '.')
{
lp--;
return s[0]; // binary '+' or '-'
}
else // c is digit or '.'
{
if (s[i] == '-') {i++;} // s[0]
// if (s[i] == '+') {} // skip adding '+' to s[]
s[i] = c; // digit or '.'
}
}

if (isdigit(c)) // collect integer part
{ // s[i] contains c (digit)
while (isdigit(s[++i] = c = line[lp++]))
{}
} // here s[i] is not a digit
if (c == '.') // collect fraction part
{ // s[i] contains c (s[i] == '.')
while (isdigit(s[++i] = c = line[lp++]))
{}
} // here s[i] is not a digit
s[i] = '\0'; // end string containing number (operand)

lp--; // line[] does not contain EOF

return NUMBER;
}

/*
gcc getcalc.c -o getcalc
./getcalc
1 2 +
3
1 2 + 3 * 4 /
2.25
-1 2 +
1
-1 -2 +
-3
-1 2 + 3 *
3
2 0 /
Error: zero divisor
2 // pop() called by '\n' returns first operand, still on stack
1 2 /
0.5
2 3 %
2
4 2 %
0
4 1 %
0
4 3 %
1
4 0 %
Error: zero divisor
Error: stack empty // nothing pushed, '\n' calls pop():
0 // value returned by pop() for an empty stack
a b +
Unknown command: a
Unknown command: b
Error: stack empty // nothing pushed, pop() called by +
Error: stack empty // pop() called by '\n'
0 // value returned by pop() for an empty stack
1 2 9
9 // pop() called by '\n'
+
3 // 1 + 2
1 2 c
Unknown command: c
2 // pop() called by '\n'
3 +
4 // 1 + 3
CTRL^D (EOF) or CTRL^C in Linux, CTRL^Z + Enter in Windows

./getcalc < compute.txt
3 // 1 2 +
0 // 1 2 * 3 + 5 -
1 // -1 2 +
1 // -1 -2 -
1 // 4 3 %
Error: zero divisor // 2 0 %
Error: stack empty // nothing pushed, '\n' calls pop():
0 // value returned by pop() for an empty stack
0.33333333 // 1 3 /
Error: zero divisor // 1 0 /
1 // pop() called by '\n' returns first operand, still on stack
*/





Notes:

See Exercise_4-3 for the file compute.txt.

We have made line[] and lp (line pointer) global variables as we use them in both main() and getop(). The alternative would be to declare them in main() and send them as arguments to getop().

In getop(), we always write line[lp++]; one should be consistent.
while (isdigit(s[++i] = c = line[lp])) {lp++;} followed by lp--; at the end of getop() is not correct. Thus, when line[lp] is not a digit, the test inside while() fails and lp++; is not executed. However, lp--; is executed and then lp points to the last digit of the last read number. The next getop() reads this digit as a separate number, then executes lp--; then the next getop() reads the same digit, etc., entering an infinite loop.









Chapter_4     Exercises_4-8,9 BACK_TO_TOP files     Exercise_4-11



Comments

Popular posts from this blog

Contents

Blogger Page Margins in Contempo