: Learning GNU Emacs, 3rd Edition

11.5.3 The Calculator Mode

11.5.3 The Calculator Mode

The complete Lisp code for the calculator mode appears at the end of this section; you should refer to it while reading the following explanation. If you download or type the code in, you can use the calculator by typing M-x calc-mode Enter. You will be put in the buffer *Calc*. You can type a line of numbers and operators and then type C-j to evaluate the line. Table 11-7 lists the three commands in calculator mode.

Table11-7.Calculator mode commands

Command Action
= Print the value at the top of the stack.
p Print the entire stack contents.
c Clear the stack.

Blank spaces are not necessary, except to separate numbers. For example, typing this:

4 17*6-=

followed by C-j, evaluates (4 * 17) - 6 and causes the result, 62, to be printed.

The heart of the code for the calculator mode is the functions calc-eval and calc-next-token. (See the code at the end of this section for these.) calc-eval is bound to C-j in Calculator mode. Starting at the beginning of the line preceding C-j, it calls calc-next-token to grab each token (number, operator, or command letter) in the line and evaluate it.

calc-next-token uses a cond construct to see if there is a number, operator, or command letter at point by using the regular expressions calc-number-regexp, calc-operator-regexp, and calc-command-regexp. According to which regular expression was matched, it sets the variable calc-proc-fun to the name (symbol) of the function that should be run (either calc-push-number, calc-operate, or calc-command), and it sets tok to the result of the regular expression match.

In calc-eval, we see where the idea of a function as a list comes in. The funcall function reflects the fact that there is little difference between code and data in Lisp. We can put together a list consisting of a symbol and a bunch of expressions and evaluate it as a function, using the symbol as the function name and the expressions as arguments; this is what funcall does. In this case, the following:

(funcall calc-proc-fun tok)

treats the symbol value of calc-proc-fun as the name of the function to be called and calls it with the argument tok. Then the function does one of three things:

If the token is a number, calc-push-number pushes the number onto the stack.

If the token is an operator, calc-operate performs the operation on the top two numbers on the stack (see below).

If the token is a command, calc-command performs the appropriate command.

The function calc-operate takes the idea of functions as lists of data a step further by converting the token from the user directly into a function (an arithmetic operator). This step is accomplished by the function read, which takes a character string and converts it into a symbol. Thus, calc-operate uses funcall and read in combination as follows:

(defun calc-operate (tok)
(let ((op1 (calc-pop))
(op2 (calc-pop)))
(calc-push (funcall (read tok) op2 op1))))

This function takes the name of an arithmetic operator (as a string) as its argument. As we saw earlier, the string tok is a token extracted from the *Calc* buffer, in this case, an arithmetic operator such as + or *. The calc-operate function pops the top two arguments off the stack by using the pop function, which is similar to the use of cdr earlier. read converts the token to a symbol, and thus to the name of an arithmetic function. So, if the operator is +, then funcall is called as here:

(funcall '+ op2 op1)

Thus, the function + is called with the two arguments, which is exactly equivalent to simply (+ op2 op1). Finally, the result of the function is pushed back onto the stack.

All this voodoo is necessary so that, for example, the user can type a plus sign and Lisp automatically converts it into a plus function. We could have done the same thing less elegantlyand less efficientlyby writing calc-operate with a cond construct (as in calc-next-token), which would look like this:

(defun calc-operate (tok)
(let ((op1 (calc-pop))
(op2 (calc-pop)))
(cond ((equal tok "+")
(+ op2 op1))
((equal tok "-")
(- op2 op1))
((equal tok "*")
(* op2 op1))
((equal tok "/")
(/ op2 op1))
(t
(% op2 op1)))))

The final thing to notice in the calculator mode code is the function calc-mode, which starts the mode. It creates (and pops to) the *Calc* buffer. Then it kills all existing local variables in the buffer, initializes the stack to nil (empty), and creates the local variable calc-proc-fun (see the earlier discussion). Finally it sets Calculator mode as the major mode, sets the mode name, and activates the local keymap.


: 0.236. /Cache: 3 / 0