Exercise 4-6

Add commands for handling variables. (It's easy to provide twenty-six variables with single-letter names.) Add a variable for the most recently printed value.

Before we implement anything, let us first think of how we can add support for variables. We can use uppercase letters for variable names (as we already use some lowercase letters for commands), and perhaps we could use '=' as the assignment operator. Then, whenever a variable name is entered, we can have its numerical value pushed to the stack. However, we have run into a problem: if a variable name is converted to its numerical value as soon as it is entered, how do we assign values to it in the first place? For simple definitions like A 1 = (A = 1 in infix notation), there may be workarounds, but our code would become really messy when we start to account for situations like A B C + = (A = B + C in infix notation.) Therefore, the easiest way to deal with this is to introduce the dereferencing operator '&': A now refers to the name of a variable, and &A represents the numerical value of 'A'.

We start by defining the floating-point array var, which holds the values of all the variables. We will set it up in a way so that var[1] holds the value of the variable 'A', var[2] holds the value of 'B', and so on. We need to create two functions to handle with variables: setvar, which takes in a variable name and assigns a value to it, and getval, which returns the value of a variable. For setvar, all we need to do is assign the value argument to var[c - 'A'], where c is the name of the variable. For getval, we simply return var[c - 'A']. For both functions, we make sure that c is a valid variable name (i.e. an uppercase letter.)

    ...
    /* setvar:  set the value of a variable */
    void setvar(int c, double f)
    {
        if (isupper(c))
            var[c - 'A'] = f;
        else
            printf("error: invalid variable name\n");
    }
    
    /* getval:  return value of a variable */
    double getval(int c)
    {
        if (isupper(c))
            return var[c - 'A'];
        else {
            printf("error: invalid variable name\n");
            return 0;
        }
    }
    ...

Now we need to add commands to utilize these functions. First off, whenever getop reads in an uppercase letter, we return a VAR token. To add support for the '&' operator, whenever it is entered, we also read in the next character (which should be the name of a variable) into s[1], so that it can be used later.

    ...
    #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
    ...
    /* getop:  get next operator or numeric operand */
    int getop(char s[])
    {
        int i, c;

        while ((s[0] = c = getch()) == ' ' || c == '\t')
            ;
        s[1] = '\0';
        if (!isdigit(c) && c != '.' && c != '+' && c != '-') {
            if (islower(c)) {
                switch (c) {
                    case 's':
                        return (lookfor("in")) ? SIN : c;
                    case 'e':
                        return (lookfor("xp")) ? EXP : c;
                    case 'p':
                        return (lookfor("ow")) ? POW : c;
                    default:
                        return c;   /* not a number */
                }
            } else if (c == '&') {
                s[1] = getch();
                s[2] = '\0';
            } else if (isupper(c))
                return VAR;
            return c;
        }
        ...

Inside main, we can now add cases for our new tokens. Firstly, when the loop encounters a VAR token, we assign s[0] (which holds the name of the variable) to temp. temp will be used when the '=' operator is entered, where the top value of stack is assigned to variable temp. Additionally, we can also give the declaration statement itself the value by pushing it so that statements like A 1 = 1 + ((A = 1) + 1 in infix notation) are also valid. Finally, when we come across '&', we push the value of the variable s[1].

    ...
    /* reverse Polish calculator */
    main()
    {
        int type, temp;
        double op1, op2;
        char s[MAXOP];
    
        while ((type = getop(s)) != EOF) {
            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 '%':
                op2 = pop();
                if (op2 != 0.0) {
                    for (op1 = pop(); op1 >= op2; op1 -= op2)
                        ;
                    push(op1);
                } else
                    printf("error: zero divisor\n");
                break;
            case SIN:
                push(sin(pop()));
                break;
            case EXP:
                push(exp(pop()));
                break;
            case POW:
                op2 = pop();
                push(pow(pop(), op2));
                break;
            case VAR:
                temp = s[0];
                break;
            case '&':
                push(getval(s[1]));
                break;
            case '=':
                setvar(temp, pop());
                push(getval(temp));
                break;
            ...

Finally, we need to create a variable for the most recently printed value. Seeing as this variable is special, we can store it inside its own variable ans and have it be pushed when 'a' is entered. Whenever a value is printed (which can occur when print_val is called or a newline is entered), we need to assign said value to ans.

    ...
    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 */
    
    /* reverse Polish calculator */
    main()
    {
        int type, temp;
        double op1, op2;
        char s[MAXOP];
    
        while ((type = getop(s)) != EOF) {
            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 '%':
                op2 = pop();
                if (op2 != 0.0) {
                    for (op1 = pop(); op1 >= op2; op1 -= op2)
                        ;
                    push(op1);
                } else
                    printf("error: zero divisor\n");
                break;
            case SIN:
                push(sin(pop()));
                break;
            case EXP:
                push(exp(pop()));
                break;
            case POW:
                op2 = pop();
                push(pow(pop(), op2));
                break;
            case VAR:
                temp = s[0];
                break;
            case '&':
                push(getval(s[1]));
                break;
            case '=':
                setvar(temp, pop());
                push(getval(temp));
                break;
            case 'a':
                push(ans);
                break;
            case 'p':
                print_val();
                break;
            case 'd':
                duplicate();
                break;
            case 's':
                swap();
                break;
            case 'c':
                clear();
                break;
            case '\n':
                if (sp == 1) {
                    printf("\t%.8g\n", op1 = pop());
                    ans = op1;
                }
                break;
            default:
                printf("error: unknown command %s\n", s);
                break;    
            }
        }
        return 0;
    }
    ...
    /* print_val:  print top element of value stack */
    void print_val(void)
    {
        if (sp > 0) {
            printf("\t%.8g\n", val[sp - 1]);
            ans = val[sp - 1];
        } else
            printf("error: stack empty\n");
    }