Jump to: navigation, search

The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 4.10 on page 79

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



Solution by Franz Fritsche

/* K&R Exercise 4-10 */
/* Franz Fritsche */

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

#define MAXOP    100  /* max size of operand or operator */
#define NUMBER   '0'  /* signal that a number was found */
#define MAXLINE 1000

int getop(char []);
void push(double);
double pop(void);
int getline(char [], int);

char line[MAXLINE];
int line_i;

/* reverse Polish calculator */
int main(void)
{
    int type;
    double op2;
    char s[MAXOP];

    while (getline(line, MAXLINE) != 0)
    {
        line_i = 0;
        while ((type = getop(s)) != '\0')
        {
            switch (type)
            {
                case NUMBER:
                    push(atof(s));
                    break;
                case '+':
                    push(pop() + pop());
                    break;
                case '*':
                    push(pop() * pop());
                    break;
                case '-':
                    op2 = pop();
                    push(pop() - op2);
                    break;
                case '/':
                    op2 = pop();
                    if (op2 != 0.0)
                        push(pop() / op2);
                    else
                        printf("error: zero divisor\n");
                    break;
                case '\n':
                    printf("\t%.8g\n", pop());
                    break;
                default:
                    printf("error: unknown command \'%s\'\n", s);
                    break;
            }
        }
    }
    
    return 0;
}

#define MAXVAL   100  /* maximum depth of val stack */

int sp = 0;           /* next free stack position */
double val[MAXVAL];   /* value stack */

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

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

    return 0.0;
}

/* getline:  get line into s, return length */
int getline(char s[], int lim)
{
    int c, i;

    i = 0;
    while (--lim > 0 && (c = getchar()) != EOF && c != '\n')
        s[i++] = c;
    if (c == '\n')
        s[i++] = c;
    s[i] = '\0';

    return i;
}

#include <ctype.h>

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

    while ((s[0] = c = line[line_i++]) == ' ' || c == '\t')
        ;
    s[1] = '\0';
    if (!isdigit(c) && c != '.')
        return c;      /* not a number */
    i = 0;
    if (isdigit(c))    /* collect integer part */
        while (isdigit(s[++i] = c = line[line_i++]))
            ;
    if (c == '.')      /* collect fraction part */
        while (isdigit(s[++i] = c = line[line_i++]))
              ;
    s[i] = '\0';
    line_i--;

    return NUMBER;
}

Solution by Jesus Alvarez (Category 0)

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

#define MAXOP     100        /* Max size of operand or operator. */
#define NUMBER    '0'        /* Signal that a number was found. */
#define VARIABLE  '1'
#define VARMAX     27
#define BUFSIZE 1000

int is_first_input = 0;    /* Prevents the solution from being printed on first
                              input */
double var_array[VARMAX];  /* Contains user defined variables. */

int    getop(char []);
void   push(double);
double pop(void);
double top(void);
int    clear(void);
int    swap(void);
int    elem(void);
int    dup(void);
void   sprnt(void);
void   result(void);
void   set_solution(void);
void   print_help(void);
int    mygetline(char [], int);

int main()
{
        int type;
        int i, j;
        int op3;
        double topd;
        double op2;
        char ic[MAXOP]; /* Input Char */
        char tmp[MAXOP];

        for (i = 0; i < VARMAX; i++) {
                var_array[i] = 0;
        }

        print_help();

        while ((type = getop(ic)) != EOF) {

                op3 = elem();
                if (op3 == 0) { /* Only one input completed. */
                        is_first_input = 1;
                } else if (op3 > 1) {
                        is_first_input = 0;
                }

                i = j = 0;

                switch (type) {
                case NUMBER:
                        push(atof(ic));
                        break;
                case VARIABLE:
                        for (i = 2; ic[i] != '\0'; i++){
                                tmp[j++] = ic[i];
                                tmp[j] = '\0';
                        }
                        var_array[ic[0] - 'A'] = atof(tmp);
                        break;
                case '+':
                        push(pop() + pop());
                        break;
                case '*':
                        push(pop() * pop());
                        break;
                case '-':
                        op2 = pop();
                        push(pop() - op2);
                        break;
                case '/':
                        op2 = pop();
                        if (op2 != 0.0){
                                push(pop() / op2);
                        } else {
                                printf("Error: Divide by zero.\n");
                        }
                        break;
                case '%':
                        op3 = (int) pop();
                        push((int) pop() % op3);
                        break;
                case 'c':
                        if (clear()) {
                                printf("Stack Cleared.\n");
                        }
                        break;
                case 'p':
                        if ((topd = top()) != 0) {
                                printf("Top stack element: %g", topd);
                                printf(" of %d elements.\n", elem());
                        }
                        break;
                case 's':
                        if (swap()) {
                                printf("Swap successful.\n");
                        }
                        break;
                case 'd':
                        if (dup()) {
                                printf("Duplication is successful.\n");
                        } else {
                                printf("Error: Stack empty.\n");
                        }
                        break;
                case 'r':
                        sprnt();
                        break;
                case 'o':
                        if (elem() < 2) {
                                printf("Error: pow requires at least two ");
                                printf("items on the stack.\n");
                                break;
                        }
                        op2 = pop();
                        push(pow(op2, pop()));
                        break;
                case 'i':
                        set_solution();
                        push(sin(pop()));
                        result();
                        break;
                case 'y':
                        set_solution();
                        push(cos(pop()));
                        break;
                case 't':
                        set_solution();
                        push(tan(pop()));
                        break;
                case 'x':
                        set_solution();
                        push(exp(pop()));
                        break;
                case 'q':
                        set_solution();
                        push(sqrt(pop()));
                        break;
                case 'f':
                        set_solution();
                        push(floor(pop()));
                        break;
                case 'l':
                        set_solution();
                        push(ceil(pop()));
                        break;
                case 'v':
                        for (i = 0; i < VARMAX; i++) {
                        if (i < VARMAX-1) {
                                printf("%c: %10.10G\n", 'A' + i, var_array[i]);
                        } else {
                                printf("%c: %10.10G\n", '=', var_array[VARMAX]);
                        }
                        }
                        break;
                case 'h':
                        print_help();
                        break;
                case '\n':
                        result();
                        break;
                default:
                        if ((type >= 'A' && type <= 'Z') || type == '=') {
                                if (type != '=') {
                                        push(var_array[type - 'A']);
                                } else {
                                        push(var_array[VARMAX]);
                                }
                        } else {
                                printf("Error: Unknown command \'%s\'\n", ic);
                        }
                        break;

                }
        }
        return 0;
}

#define MAXVAL  100     /* Maximum depth of val stack. */
int sp = 0;             /* Next free stack position. */
double val[MAXVAL];     /* Value stack. */

void push(double f)
{
        if (sp < MAXVAL) {
                val[sp++] = f;
        } else {
                printf("Error: Stack full, cannot push %g\n", f);
        }
}

double pop(void)
{
        if (sp > 0) {
                return val[--sp];
        } else {
                printf("Error: Stack empty.\n");
                return 0.0;
        }
}

double top(void)
{
        if (sp > 0) {
                return val[sp-1];
        } else {
                printf("Error: Stack empty.\n");
                return 0.0;
        }
}

int clear(void)
{
        if (sp > 0) {
                while(val[--sp] != '\0');
                sp = 0;
                return 1;
        } else {
                printf("Error: Stack empty.\n");
                return 0;
        }
}

int swap(void)
{
        double sbuf;
        if (sp > 0) {
                sbuf = val[sp-2];
                val[sp-2] = val[sp-1];
                val[sp-1] = sbuf;
                return 1;
        } else {
                printf("Error: Stack empty.\n");
                return 0;
        }
}

int elem(void)
{
        return sp;
}

int dup (void)
{
        if (sp > 0) {
                sp++;
                val[sp] = val[sp-1];
                return 1;
        } else {
                return 0;
        }
}

void sprnt(void)
{
        int count = 0;
        while (count < sp) {
                printf("%d:%10.12g\n", count+1, val[count]);
                count++;
        }
}

void result(void)
{
        if (sp == 1 && is_first_input != 1) {
                printf("Solution: %10.20g\n", val[0]);
                var_array[VARMAX] = val[0];
                is_first_input = 0;
                clear();
        }
}

/*
 * Opens result() for execution.
 * Primarily used with the math functions because they can be used with only
 * one stack item. For ex, if "1 i" is entered as the first input, this
 * function would allow for a result to be shown and the stack cleared.
 */
void set_solution(void)
{
        if (elem() >= 1) {
                is_first_input = 0;
        }
}

int mygetline(char s[], int maxline)
{
        int i = 0;
        char c;
        while ((c = getchar()) != EOF && c != '\n' && i < maxline) {
                s[i++] = c;
        }
        if (c == '\n') {
                s[i++] = c;
        }
        s[i++] = '\0';
        return i;
}

char getopbuf[BUFSIZE];     /* A string buffer that holds the mygetline. */
int  getopbufp   = 0;       /* Current position is the string buffer. */
int  getopbuflen = 0;

int getop(char s[])
{
        int i = 0, c;
        s[0] = '\0';

        if (getopbuflen == 0 || getopbufp == getopbuflen-2) {
                getopbufp = 0;
                /* Get a new line of input from the user. */
                getopbuflen = mygetline(getopbuf, BUFSIZE);
        }

        if (getopbuf[getopbufp] == '\n') {
                return '\n';
        }

        /* Jump over spaces at the begining of the string. */
        while (isspace(c = getopbuf[getopbufp++]));
        s[i++] = c;

        if (isalpha(c) && c >= 'A' && c <= 'Z') {
                /* Collect the variable. */
                for (i = 1; getopbuf[getopbufp] != ' '
                     && getopbuf[getopbufp] != '\n';
                     s[i++] = getopbuf[getopbufp++]);
                s[i] = '\0';
                if (i > 1) { /* A properly formed variable definition. */
                        return VARIABLE;
                } else {
                        return c;
                }
        } else if (!isdigit(c) && c != '.' && c != '-') {
                return c; /* Not a number. */
        }

        if (c == '-') {
                if ((c = getopbuf[++getopbufp]) == ' ') {
                        /* If the next char is space, then c is a operator. */
                        return c;
                } else if (isdigit(c)) {
                        s[++i] = c;
                }

        }

        if (isdigit(c)) { /* Collect integer part. */
                for ( ; isdigit(c = getopbuf[getopbufp]); i++, getopbufp++) {
                        s[i] = c;
                }
        }

        if (c == '.') {   /* Collect fraction part. */
                while (isdigit(s[i++] = c = getopbuf[getopbufp++]));
        }

        s[i] = '\0';
        return NUMBER;
}

void print_help(void)
{
        printf("The Polish Calculator\n");
        printf("-----------------------------------------------\n");
        printf("-> Enter equations in the form: \"1 1 + 2 5 + *\"\n");
        printf("-> Use \"A=1 B=2 C=3\" to store variables.\n");
        printf("-> Use \"A B C * *\" to use stored variables.\n");
        printf("-----------------------------------------------\n");
        printf(">>> Command Help:\n");
        printf(">>>     c:      Clear memory.\n");
        printf(">>>     p:      Print last character.\n");
        printf(">>>     s:      Swap last two characters.\n");
        printf(">>>     d:      Duplicate the last input.\n");
        printf(">>>     r:      Print the entire stack.\n");
        printf(">>>     v:      Print variable list.\n");
        printf(">>>     o:      pow(x,y), x^y, x > 0.\n");
        printf(">>>     i:      sin(x), sine of x.\n");
        printf(">>>     y:      cos(x), cosine of x.\n");
        printf(">>>     t:      tan(x), tangent of x.\n");
        printf(">>>     x:      exp(x), e^x, exponential function.\n");
        printf(">>>     q:      sqrt(x), x >= 0, square of x.\n");
        printf(">>>     f:      floor(x), largest integer not greater than x.\n");
        printf(">>>     l:      ceil(x), smallest integer not less than x.\n");
        printf(">>>     =:      Access the last successful solution.\n");
        printf(">>>     h:      Print this help text.\n");
}

Solution by anonymous

I struggled to write this program on my own, so I used code from http://www.learntosolveit.com/cprogramming/Ex_4.10_calculator_getline.html as a guide. I originally attempted a much more complex method of doing this, but I discovered there was a much simpler way of replacing getch/ungetch with getline. Below are the major changes from my exercise 4.6 program and this one:

int getop(char s[])
{

// previous
    i = 0;

    while ((s[0] = c = getch()) == ' ' || c == '\t') // skip white space
// new
    i = 0;

    if (line[linep] == '\0') // if at end of line or is first time reaching this since array is initialized to '\0'
    {
        if (getline(line, MAXLINE) == 0) // if == 0, reached EOF. So return it
            return EOF;
        else // otherwise, reset line position to zero to process new line
            linep = 0;
    }

    while ((s[0] = c = line[linep++]) == ' ' || c == '\t') // skip white space

For the rest of the getop function, all getch() was replaced with line[linep++] and all ungetch(c) was replaced with linep--. That is basically it. The rest of the minor changes were replacing buf with line, bufp with linep, BUFSIZE with LINESIZE, removing getch, ungetch, and adding getline. Here is the whole program:

#include <stdio.h>
#include <stdlib.h> // for atof()
#include <ctype.h>
#include <math.h>
#include <string.h> // for strlen()

/*
    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.
*/

#define MAXOP 100       // max size of operand or operator
#define NUMBER '0'      // signal that a number was found
#define SETVARIABLE 's' // signal that a variable is being assigned
#define GETVARIABLE 'g' // signal that a variable is being retrieved
#define ERRORSIGNAL ' ' // signal that an error occurred in getop
#define MAXVAL 100      // maximum depth of val stack
#define MAXLINE 1000    // buffer size for line
#define NUMVARS 27      // number of variables supported (a-z) plus the variable L for last printed

enum boolean {FALSE, TRUE};

int sp = 0;                      // next free stack position
double val[MAXVAL];              // value stack
char line[MAXLINE] = {'\0'};     // buffer for getline. Set all values to zero from the start for logic below
int linep = 0;                   // current position in line buffer
double varVals[NUMVARS] = {0.0}; // array to store the double values for variables. Supports variables a through z (lower case). Initial value is 0

int getop(char s[]);
void push(double f);
double pop(void);
void printTop(void);
void duplicateTop(void);
void swapTopTwo(void);
int getline(char s[], int lim);

// reverse Polish calculator
// note: convert ((((-1 - 2) * (4 + -5)) / -3) % 5) * (-1 - -10) to -1 2 - 4 -5 + * -3 / 5 % -1 -10 - * for reverse Polish notation. -1 2 - 4 -5 + * -3 / 5 % -1 -10 - * == -9
int main()
{
    int type;
    double op2;
    char s[MAXOP];
    char skipNextNewline = FALSE;

    while ((type = getop(s)) != EOF)
    {
        switch (type)
        {
        case NUMBER:
            push(atof(s)); // convert the string to type double and push it on the stack
            break;
        case SETVARIABLE:
            if (strlen(s) > 2 && s[1] == '=')
            {
                int v = s[0];           // stores variable
                int i = 1;              // start at 1 since while loop needed ++i for a few reasons
                while (s[++i] != '\0')  // this removes the variable name= part of s e.g. if s == "a=123.45" after loop s == "123.45"
                    s[i - 2] = s[i];    // shifts chars two to the left by 2
                s[i - 2] = '\0';        // since '\0' isn't copied, terminate string manually
                varVals[v - 'a'] = atof(s); // convert string to double and store it in array
            }
            else
                printf("error: set variable length too small or incorrectly formatted (%s)\n", s);

            skipNextNewline = TRUE;
            break;
        case GETVARIABLE:
            push(varVals[s[0] - 'a']);  // convert the variable name to stored value
            break;
        case '+':
            push(pop() + pop()); // pop last two digits to sum them and push the result on the stack
            break;
        case '*':
            push(pop() * pop()); // pop last two digits to multiply them and push the result on the stack
            break;
        case '-':
            /*
                Because + and * are commutative operators, the order in which the popped operands are combined is irrelevant, but for - and / the left and right operands
                must be distinguished. In push(pop() - pop());, the order in which the two calls of pop are evaluated is not defined. To guarantee the right order, it is
                necessary to pop the first value into a temporary variable. Hence op2 = pop() in - and / but not in + and *
            */
            op2 = pop();
            push(pop() - op2); // pop last two digits to subtract them in the correct order and push the result on the stack
            break;
        case '/':
            op2 = pop();
            if (op2 != 0.0)
                push(pop() / op2); // pop last two digits to divide them in the correct order and push the result on the stack
            else
                printf("error: zero divisor\n");
            break;
        case '%':
            op2 = pop();
            if (op2 != 0.0)
                push(fmod(pop(), op2)); // pop last two digits in the correct order to find the modulus and push the result on the stack
            else
                printf("error: zero divisor\n");
            break;
        case '?':
            printTop();
            skipNextNewline = TRUE;
            break;
        case '#':
            duplicateTop();
            skipNextNewline = TRUE;
            break;
        case '~':
            swapTopTwo();
            skipNextNewline = TRUE;
            break;
        case '!':
            // sets next free stack position to zero (meaning the value stack is empty).
            // all of the original values are still there, but they will no longer be accessible by the current functions and they will be overwritten when new elements are stored
            sp = 0;
            skipNextNewline = TRUE;
            break;
        case '$':
            push(sin(pop()));
            skipNextNewline = TRUE;
            break;
        case '@':
            push(exp(pop()));
            skipNextNewline = TRUE;
            break;
        case '^':
            op2 = pop();
            push(pow(pop(), op2)); // pop last two digits in the correct order to raise a number to the given power
            skipNextNewline = TRUE;
            break;
        case '&':
            push(sqrt(pop()));
            skipNextNewline = TRUE;
            break;
        case ',':
            push(fabs(pop()));
            skipNextNewline = TRUE;
            break;
        case 'L':
            push(varVals[NUMVARS - 1]); // adds the last printed value to the stop of the stack
            break;
        case '\n':
            if (skipNextNewline)
                skipNextNewline = FALSE;
            else
            {
                varVals[NUMVARS - 1] = pop(); // updates last printed value
                printf("\t%.8g\n", varVals[NUMVARS - 1]); // get the final result
            }
            break;
        default:
            printf("error: unknown command %s\n", s);
            break;
        }
    }
    return 0;
}

// push: push f onto value stack
void push(double f)
{
    if (sp < MAXVAL) // if value stack still has space, add f
        val[sp++] = f;
    else
        printf("error: stack full, can't push %g\n", f);
}

// pop: pop and return top value from stack
double pop(void)
{
    if (sp > 0) // if the next free stack position is greater than zero, return the highest level item from stack
        return val[--sp];
    else
    {
        printf("error: stack empty\n");
        return 0.0;
    }
}

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

    if (line[linep] == '\0') // if at end of line or is first time reaching this since array is initialized to '\0'
    {
        if (getline(line, MAXLINE) == 0) // if == 0, reached EOF. So return it
            return EOF;
        else // otherwise, reset line position to zero to process new line
            linep = 0;
    }

    while ((s[0] = c = line[linep++]) == ' ' || c == '\t') // skip white space
        ;
    s[1] = '\0'; // terminate string in case input is not a number (s is expected to be a string throughout program)
    if (c >= 'a' && c <= 'z')
    {
        if ((s[++i] = c = line[linep++]) == '=') // get next char and check if it was an equal symbol. Update s in case of error
            setVar = TRUE;
        else if (c == ' ' || c == '\t' || c == '\n')
        {
            linep--; // this moves the line position back to process the whitespace later
            return GETVARIABLE;
        }

        if (!(setVar && ((s[++i] = c = line[linep++]) == '-' || isdigit(c))))
            return ERRORSIGNAL; // triggers an error and will display what is in s
    }
    if (!isdigit(c) && c != '.' && c != '-')
        return c; // not a number. Probably an operator, so return it. Minus operator is a special case and is handled right before return NUMBER;
    if (c == '-' || isdigit(c)) // collect integer(s), if any, after first digit found or after minus symbol found
        while (isdigit(s[++i] = c = line[linep++]))
            ;
    if (c == '.') // collect fraction part if period is found
        while (isdigit(s[++i] = c = line[linep++]))
            ;
    s[i] = '\0'; // terminate string after digits were captured
    if (c != EOF)
        linep--; // since we read too far, move the line position back back one
    if (i == 1 && s[0] == '-') // if s[0] == '-' && s[1] == '\0', return minus operator
        return '-';
    if (setVar)
        return SETVARIABLE;
    else if (c >= 'a' && c <= 'z')
        return ERRORSIGNAL; // if last char is a variable and setVar wasn't set to TRUE, throw error

    return NUMBER;
}

// prints the top element in the value stack
void printTop(void)
{
    if (sp > 0)
        printf("\t%.8g\n", val[sp - 1]);
    else
        printf("error: stack empty\n");
}

void duplicateTop(void)
{
    if (sp < MAXVAL) // only need to see if there is space for one more
        push(val[sp - 1]); // duplicates top item
    else
        printf("error: stack full, can't duplicate top element\n");
}

void swapTopTwo(void)
{   // if sp == 2, there are at least two elements stored
    if (sp > 1)
    {                               // <third> <second> <first>
        double first = pop();       // <third> <second>
        double second = pop();      // <third>
        push(first);                // <third> <first>
        push(second);               // <third> <first> <second>
    }
    else
        printf("error: can't swap top two, not enough elements\n");
}

// get line into s, return length
int getline(char s[], int lim)
{
    int c, i;
    i = 0;
    while (--lim > 0 && (c=getchar()) != EOF && c != '\n')
        s[i++] = c;
    if (c == '\n')
        s[i++] = c;
    s[i] = '\0';
    return i;
}
Personal tools