(car calc-stack)))
(defun calc-pop ( )
(let ((val (calc-top)))
(if val
(setq calc-stack (cdr calc-stack)))
val))
;; functions for user commands:
(defun calc-print-stack ( )
"Print entire contents of stack, from top to bottom."
(if calc-stack
(progn
(insert "\n")
(let ((stk calc-stack))
(while calc-stack
(insert (number-to-string (calc-pop)) " "))
(setq calc-stack stk)))
(error "stack empty.")))
(defun calc-clear-stack ( )
"Clear the stack."
(setq calc-stack nil)
(message "stack cleared."))
(defun calc-command (tok)
"Given a command token, perform the appropriate action."
(cond ((equal tok "c")
(calc-clear-stack))
((equal tok "=")
(insert "\n" (number-to-string (calc-top))))
((equal tok "p")
(calc-print-stack))
(t
(message (concat "invalid command: " tok)))))
(defun calc-operate (tok)
"Given an arithmetic operator (as string), pop two numbers
off the stack, perform operation tok (given as string), push
the result onto the stack."
(let ((op1 (calc-pop))
(op2 (calc-pop)))
(calc-push (funcall (read tok) op2 op1))))
(defun calc-push-number (tok)
"Given a number (as string), push it (as number)
onto the stack."
(calc-push (string-to-number tok)))
(defun calc-invalid-tok (tok)
(error (concat "Invalid token: " tok))
(defun calc-next-token ( )
"Pick up the next token, based on regexp search.
As side effects, advance point one past the token,
and set name of function to use to process the token."
(let (tok)
(cond ((looking-at calc-number-regexp)
(goto-char (match-end 0))
(setq calc-proc-fun 'calc-push-number))
((looking-at calc-operator-regexp)
(forward-char 1)
(setq calc-proc-fun 'calc-operate))
((looking-at calc-command-regexp)
(forward-char 1)
(setq calc-proc-fun 'calc-command))
((looking-at ".")
(forward-char 1)
(setq calc-proc-fun 'calc-invalid-tok)))
;; pick up token and advance past it (and past whitespace)
(setq tok (buffer-substring (match-beginning 0) (point)))
(if (looking-at calc-whitespace)
(goto-char (match-end 0)))
tok))
(defun calc-eval ( )
"Main evaluation function for calculator mode.
Process all tokens on an input line."
(interactive)
(beginning-of-line)
(while (not (eolp))
(let ((tok (calc-next-token)))
(funcall calc-proc-fun tok)))
(insert "\n"))
(defun calc-mode ( )
"Calculator mode, using H-P style postfix notation.
Understands the arithmetic operators +, -, *, / and %,
plus the following commands:
c clear stack
= print top of stack
p print entire stack contents (top to bottom)
Linefeed (C-j) is bound to an evaluation function that
will evaluate everything on the current line. No
whitespace is necessary, except to separate numbers."
(interactive)
(pop-to-buffer "*Calc*" nil)
(kill-all-local-variables)
(make-local-variable 'calc-stack)
(setq calc-stack nil)
(make-local-variable 'calc-proc-fun)
(setq major-mode 'calc-mode)
(setq mode-name "Calculator")
(use-local-map calc-mode-map))
The following are some possible extensions to the calculator mode, offered as exercises. If you try them, you will increase your understanding of the mode's code and Emacs Lisp programming in general.
• Add an operator ^ for "power" (4 5 ^ evaluates to 1024). There is no built-in power function in Emacs Lisp, but you can use the built-in function expt.
• Add support for octal (base 8) and/or hexadecimal (base 16) numbers. An octal number has a leading "0," and a hexadecimal has a leading "0x"; thus, 017 equals decimal 15, and 0x17 equals decimal 23.
• Add operators \+ and \* to add/multiply all of the numbers on the stack, not just the top two (e.g., 4 5 6 \+ evaluates to 15, and 4 5 6 \* evaluates to 120).[82]
• As an additional test of your knowledge of list handling in Lisp, complete the example (Example 5) from earlier in this chapter that searches compilation-error-regexp-alist for a match to a compiler error message. (Hint: make a copy of the list, then pick off the top element repeatedly until either a match is found or the list is exhausted.)
11.6 Customizing Existing Modes
Now that you understand some of what goes into programming a major mode, you may decide you want to customize an existing one. Luckily, in most cases, you don't have to worry about changing any mode's existing Lisp code to do this; you may not even have to look at the code. All Emacs major modes have "hooks" for letting you add your own code to them. Appropriately, these are called mode-hooks. Every built-in major mode in Emacs has a mode hook called mode-name-hook, where mode-name is the name of the mode or the function that invokes it. For example, C mode has c-mode-hook, shell mode has shell-mode-hook, etc.
82
APL programmers will recognize these as variations of that language's "scan" operators.