Jump to: navigation, search

The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 5.10 on page 118

Write the program expr , which evaluates a reverse Polish expression from the command line, where each operator or operand is a separate argument. For example,
expr 2 3 4 + *
evaluates 2 X (3 + 4).




Solution by Lars Wirzenius

Note: Lars uses EXIT_FAILURE on error. As far as I can tell, this is the only thing which makes this a Category 1, rather than Category 0, solution.

/*
 * Solution to exercise 5-10 in K&R2:
 *
 *	Write the program expr, which evaluates a reverse Polish expression
 *	from the command line, where each operator or operand is a separate 
 *	argument. For example,
 *
 *		expr 2 3 4 + *
 *
 *	evaluates 2*(3+4).
 *
 * This is very similar to the program in 4.3 (and should ideally have been
 * a modification of that).
 *
 * Feel free to modify and copy freely.
 *
 * Lars Wirzenius <liw@iki.fi>
 */

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

#define STACK_SIZE 1024

double stack[STACK_SIZE];
int stack_height = 0;

void panic(const char *msg) {
	fprintf(stderr, "%s\n", msg);
	exit(EXIT_FAILURE);
}

void push(double value) {
	if (stack_height == STACK_SIZE)
		panic("stack is too high!");
	stack[stack_height] = value;
	++stack_height;
}

double pop(void) {
	if (stack_height == 0)
		panic("stack is empty!");
	return stack[--stack_height];
}

int main(int argc, char **argv) {
	int i;
	double value;
	
	for (i = 1; i < argc; ++i) {
		switch (argv[i][0]) {
		case '\0':
			panic("empty command line argument");
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			push(atof(argv[i]));
			break;
		case '+':
			push(pop() + pop());
			break;
		case '-':
			value = pop();
			push(pop() - value);
			break;
		case '*':
			push(pop() * pop());
			break;
		case '/':
			value = pop();
			push(pop() / value);
			break;
		default:
			panic("unknown operator");
			break;
		}
	}

	printf("%g\n", pop());
	return 0;
}


Solution by Alex Hoang (Category 0)

/* Allows for leading plus/minus as well as decimal numbers */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#define NUMBER 0

void push(double f);

double pop(void);


main(int argc, char *argv[])
{
    int type;
    int c;
    double op1, op2, latest;
    while (--argc > 0)
    {
        *++argv;
        if (!isdigit(c = **argv) && strlen(*argv) == 1)
            type = c;
        else
            type = NUMBER;
        switch (type)
        {
            case NUMBER:
                push(atof(*argv));
                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 '%':
                op2 = pop();
                if (op2 != 0.0)
                     push(fmod(pop(), op2));
                else
                    printf("error: zero divisor\n");
                break;
            case '^':
                op2 = pop();
                op1 = pop();
                if (op1 == 0.0 && op2 <= 0)
                    printf("if x = 0.0, y must be greater than 0\n");
                else
                    push(pow(op1, op2));
                break;
            case 'e':
                push(exp(pop()));
                break;
            case '~':
                push(sin(pop()));
                break;
            default:
                printf("error: unknown command: %c\n", type);
                break;
        }
    }
    latest = pop();
    printf("\t%.8g\n", latest);
    return 0;
}

#define MAXVAL 100

int sp = 0;
double val[MAXVAL];
/* maximum depth of val stack */
/* next free stack position */
/* 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;
    }
}


Solution by menonsahab

(Note for Menonsahab and readers: The reason that * is causing issues is that it is a shell wildcard. You are calling the program from your shell and the argument is passing to the shell first, which expands it into a list of all files in the current folder. If you use the escape sequence \* instead of * as an argument while calling your c program, you should get the expected result.). However, if you escape the asterisk and you are still having problems, it could be that you are using MinGW's version of gcc. See anonymous' solution to that problem.


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

#define MAXVAL 100
int sp = 0;
double val[MAXVAL];

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

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

int main(int argc, char *argv[])
{
	int i, c, op2;
	char *p;
	for(i = 1; i < argc; i++)
	{
		p = argv[i];
		printf("argv[%d] = %s\n", i, argv[i]);
		while(*p && isdigit(*p))
			p++;
		printf("*p = %d, %c\n", *p, *p);
		switch(*p)
		{
			case '\0':
				push(atoi(argv[i]));
				break;
			case '+':
				push(pop() + pop());
				break;
			case '-':
				op2 = pop();
				push(pop() - op2);
				break;
			case 'x':
				push(pop() * pop());
				break;
			case '/':
				op2 = pop();
				if(op2 != 0.0)
					push(pop() / op2);
				else
					printf("error: zero divisor\n");
				break;
		}
	}
	printf("Final result = %lf\n", pop());
	return 0;
}

/*
I noticed that the program was giving weird results when I was 
using the multiplication symbol '*' as a command line input.
I couldn't figure out why that was happening so now I'm just 
using the 'x' symbol to denote multiplication.
I even ran the other two solutions on this page but to no avail.
*/

/*
On running:
expr 2 3 4 + x
the output is:
Final result = 14.000000
*/

Solution by i9383

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

#define MAXVAL	100

main(int argc, char *argv[])
{
	double d[MAXVAL];
	double *pd = d;

	while (--argc) {
		if (isdigit(**++argv))
			*pd++ = atof(*argv);
		else if (*argv[0] == '-') {
			pd -= 2;
			*pd = *pd - *(pd+1);
			++pd;
		}
		else if (*argv[0] == '/') {
			pd -= 2;
			*pd = *pd / *(pd+1);
			++pd;
		}
		else if (*argv[0] == '*') {
			pd -= 2;
			*pd = *pd * *(pd+1);
			++pd;
		}
		else if (*argv[0] == '+') {
			pd -= 2;
			*pd = *pd + *(pd+1);
			++pd;
		}
		else
			argc = 1;
	}
	printf("%f\n", *--pd);
	return 0;
}

Solution by anonymous

I took the reverse Polish calculator program from pages 76-79 and adapted it to use command line arguments instead of relying on getch() and ungetch(). I originally had the program from exercise 4.10 working, but I figured someone could add that functionality back since I kept this solution similar to the book's version of the calculator. Plus, it is 150 lines shorter.

During the conversion I ran into an annoying issue when an asterisk (*) was given as a command line argument in Windows. It turns out that the MinGW version of gcc adds in globbing for command line arguments by default and it cannot be escaped. This is because a program compiled by it will take an asterisk and expand it inside of the program. Fortunately, this annoying documentation-lacking feature is easily disabled by setting _CRT_glob to 0. Afterwards, everything worked as expected.

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

/*
    Exercise 5-10. Write the program expr, which evaluates a reverse Polish expression from the command line, where each operator or operand is a separate argument.
    For example, expr 2 3 4 + * evaluates 2 x (3 + 4).
*/

extern int _CRT_glob = 0;    // disables MinGW globbing that is added to this program. Source: https://willus.org/mingw/_globbing.shtml

#define MAXOP 100    // max size of operand or operator
#define NUMBER '0'   // signal that a number was found
#define MAXVAL 100   // maximum depth of val stack
#define MAXLINE 1000 // buffer size for line

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

int getop(char s[]);
void push(double f);
double pop(void);

// reverse Polish calculator
int main(int argc, char *argv[])
{
    int type, c, i = 0;
    double op2;
    char s[MAXOP];

    if (argc < 2) // if no arguments were given, print usage and exit
    {
        printf("Usage: expr <reverse Polish expression>\n");
        return -1;
    }
    while (--argc > 0) // while there are arguments left, add them to the line char array
    {
        argv++; // This prevents the first argument from being processed and moves to next argument on each iteration
        while ((c = *(*argv)++)) // gets each character in the argument until it reaches '\0'
            line[i++] = c;
        line[i++] = ' '; // space delimit the arguments for the getop function
    }
    line[i++] = '\n'; // since the switch below expects a newline character to print the data, add it at the end
    line[i] = '\0'; // then terminate the string

    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 '+':
            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 '-':
            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 '\n':
            printf("%.8g\n", pop()); // print the final result
            break;
        default:
            printf("error: unknown command %s\n", s);
            break;
        }
    }
    return 0;
}

// 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 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. Update the pointer to point to the item below it
        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 = 0, c;
    if (line[linep] == '\0') // if at end of line return EOF to terminate the loop in main
        return EOF;
    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 (!isdigit(c) && c != '.')
        return c; // not a number. Probably an operator, so return it.
    if (isdigit(c)) // collect integer(s), if any, after first digit 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 one
    return NUMBER;
}
Personal tools