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.

For this exercise, we will start from the code in exercise 4-6, as using getline would render additions made in following exercises irrelevant.

Seeing as switching to getline is a major change, let us first identify what can be removed from our program. We obviously no longer need getch and ungetch, but this also means we can remove buf and the variables associated with it.

If we look at our program currently, we notice that getch and ungetch are almost exclusively used in getop, so in theory, if we can adapt getop to use getline and make sure we execute getline in main some way, we should not need to make any more changes. Let us tackle the latter first. We start by introducing the global variable line to store the current input line. We also create the global index lp to keep track of which position on line we are currently on. Then, in main, we nest the while-loop already present inside another while-loop that executes getline until EOF is reached. This means we also need to change the condition of the inner while-loop to execute getop until a newline is reached and move the newline case in the switch statement to after the loop so that it gets executed after the inner while-loop terminates. Finally, before getline is called again, we reset lp to zero.

    #include <stdio.h>
    #include <stdlib.h> /* for atof() */
    #include <ctype.h>
    #include <math.h>
    
    #define MAXOP   100     /* max size of operand or operator */
    #define NUMBER  '0'     /* signal that a number was found */
    #define VAR     'A'     /* signal that a variable was found */
    #define SIN     'I'     /* signal that a sin function was found */
    #define EXP     'E'     /* signal that a exp function was found */
    #define POW     'O'     /* signal that a pow function was found */
    #define MAXVAL  100     /* maximum depth of val stack */
    #define BUFSIZE 100
    #define MAXLINE 1000    /* maximum input line size */
    
    int getop(char []);
    void push(double);
    double pop(void);
    void print_val(void);
    void duplicate(void);
    void swap(void);
    void clear(void);
    void setvar(int, double);
    double getval(int);
    int getch(void);
    void ungetch(int);
    int my_getline(char s[], int lim);
    int lookfor(char []);
    
    int sp = 0;         /* next free stack position */
    int ans = 0;        /* most recently printed value */
    double val[MAXVAL]; /* value stack */
    double var[26];     /* variable values */
    char buf[BUFSIZE];  /* buffer for ungetch */
    int bufp = 0;       /* next free position in buf */
    char line[MAXLINE]; /* current input line */
    int lp = 0;         /* current position in line */
    
    /* reverse Polish calculator */
    main()
    {
        int type, temp;
        double op1, op2;
        char s[MAXOP];
    
        while (my_getline(line, MAXLINE) > 0) {
            while ((type = getop(s)) != EOF) {
            while ((type = getop(s)) != '\n') {
                switch (type) {
                ...
                case '\n':
                    if (sp == 1) {
                        printf("\t%.8g\n", op1 = pop());
                        ans = op1;
                    }    
                default:
                    printf("error: unknown command %s\n", s);
                    break;
                }
            }
            if (sp == 1) {
                printf("\t%.8g\n", op1 = pop());
                ans = op1;
            }
            lp = 0;
        }
        return 0;
    }
    ...

With the groundwork laid down, altering getop to use getline becomes simple. We replace every call of getch with ++lp (and seeing as we usually assign the return value to another variable, we can assign line[++lp] instead), and every call of ungetch with --lp. Also, there is no longer a need for c, as its value can be represented as line[lp]. Seeing as we increment lp at the start of getop, we now initialize and reset lp to -1 every time we read in a new line so that the first character of a line does not get skipped. Finally, as we also use getch and ungetch in lookfor, we replace its function calls using the same idea since line and lp are global.

    ...
    int sp = 0;         /* next free stack position */
    int ans = 0;        /* most recently printed value */
    double val[MAXVAL]; /* value stack */
    double var[26];     /* variable values */
    char line[MAXLINE]; /* current input line */
    int lp = 0;         /* current position in line */
    int lp = -1;        /* current position in line */
    
    /* reverse Polish calculator */
    main()
    {
        int type, temp;
        double op1, op2;
        char s[MAXOP];

        while (my_getline(line, MAXLINE) > 0) {
            ...
            if (sp == 1) {
                printf("\t%.8g\n", op1 = pop());
                ans = op1;
            }
            lp = 0;
            lp = -1;
        }
        return 0;
    }
    ...
    /* getop:  get next operator or numeric operand */
    int getop(char s[])
    {
        int i, c;

        while ((s[0] = c = getch()) == ' ' || c == '\t')
        while ((s[0] = line[++lp]) == ' ' || line[lp] == '\t')
            ;
        s[1] = '\0';
        if (!isdigit(c) && c != '.' && c != '+' && c != '-') {
            if (islower(c)) {
                switch (c) {
        if (!isdigit(line[lp]) && line[lp] != '.' && line[lp] != '+' &&
            line[lp] != '-') {
            if (islower(line[lp])) {
                switch (line[lp]) {
                    case 's':
                        return (lookfor("in")) ? SIN : c;
                        return (lookfor("in")) ? SIN : line[lp];
                    case 'e':
                        return (lookfor("xp")) ? EXP : c;
                        return (lookfor("xp")) ? EXP : line[lp];
                    case 'p':
                        return (lookfor("ow")) ? POW : c;
                        return (lookfor("ow")) ? POW : line[lp];
                    default:
                        return c;   /* not a number */
                        return line[lp];    /* not a number */
                }
            } else if (c == '&') {
                s[1] = getch();
            } else if (line[lp] == '&') {
                s[1] = line[++lp];
                s[2] = '\0';
                return '&';
            } else if (isupper(c))
            } else if (isupper(line[lp]))
                return VAR;
            return c;
            return line[lp];
        }
        i = 0;
        if (isdigit(c) || c == '+' || c == '-') /* collect integer part */
            while (isdigit(s[++i] = c = getch()))
                ;
        if (c == '.')   /* collect fraction part */
            while (isdigit(s[++i] = c = getch()))
                ;
        /* collect integer part */
        if (isdigit(line[lp]) || line[lp] == '+' || line[lp] == '-')
            while (isdigit(s[++i] = line[++lp]))
                ;
        if (line[lp] == '.') /* collect fraction part */
            while (isdigit(s[++i] = line[++lp]))
                ;
        s[i] = '\0';
        if (c != EOF)
            ungetch(c);
        if (line[lp] != EOF)
            --lp;
        if ((s[0] == '+' || s[0] == '-') && i == 1)
            return s[0];    /* return operator if no digits afterwards */
        return NUMBER;
    }
    ...
    /* lookfor:  return 1 if specified string is found */
    int lookfor(char s[])
    {
        int i, j;
        int temp[MAXOP];

        for (i = 0; s[i] != '\0' && (temp[i] = getch()) == s[i]; ++i)
        for (i = 0; s[i] != '\0' && line[++lp] == s[i]; ++i)
            ;
        if (s[i] == '\0')   /* string was found */
            return 1;
        else {  /* unget read characters and return 0 */
            for (j = 0; j <= i; ++j)
                ungetch(temp[j]);
            lp -= i + 1;
            return 0;
        }
    }