Выбрать главу

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 elegantly—and less efficiently—by 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.

11.5.4 Lisp Code for the Calculator Mode

Now you should be able to understand all of the code for the calculator mode. You will notice that there really isn't that much code at all! This is testimony to the power of Lisp and the versatility of built-in Emacs functions. Once you understand how this mode works, you should be ready to start rolling your own. Without any further ado, here is the code:

;; Calculator mode.

;;

;; Supports the operators +, -, *, /, and % (remainder).

;; Commands: ;; c clear the stack

;; = print the value at the top of the stack

;; p print the entire stack contents

;;

(defvar calc-mode-map nil

  "Local keymap for calculator mode buffers.")

; set up the calculator mode keymap with

; C-j (linefeed) as "eval" key

(if calc-mode-map

    nil

  (setq calc-mode-map (make-sparse-keymap))

  (define-key calc-mode-map "\C-j" 'calc-eval))

(defconst calc-number-regexp

  "-?\\([0-9]+\\.?\\|\\.\\)[0-9]*\\(e[0-9]+\\)?"

  "Regular expression for recognizing numbers.")

(defconst calc-operator-regexp "[-+*/%]"

  "Regular expression for recognizing operators.")

(defconst calc-command-regexp "[c=ps]"

  "Regular expression for recognizing commands.")

(defconst calc-whitespace "[ \t]"

  "Regular expression for recognizing whitespace.")

;; stack functions

(defun calc-push (num)

  (if (numberp num)

  (setq calc-stack (cons num calc-stack))))

(defun calc-top ( )

  (if (not calc-stack)

      (error "stack empty.")