(read_minibuf): Clean up the binding stack if
[bpt/emacs.git] / lisp / calculator.el
CommitLineData
25c269ef 1;;; calculator.el --- A [not so] simple calculator for Emacs.
d240a249 2
25c269ef 3;; Copyright (C) 1998, 2000 by Free Software Foundation, Inc.
d240a249 4
4ece8d61 5;; Author: Eli Barzilay <eli@barzilay.org>
d240a249 6;; Keywords: tools, convenience
cbb2dddb 7;; Time-stamp: <2000-11-19 20:59:59 eli>
d240a249
GM
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify it
12;; under the terms of the GNU General Public License as published by the
13;; Free Software Foundation; either version 2, or (at your option) any
14;; later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful, but
17;; WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19;; General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
24;; MA 02111-1307, USA.
25
cbb2dddb 26;;;=====================================================================
d240a249
GM
27;;; Commentary:
28;;
25c269ef 29;; A calculator for Emacs.
5ca425fc 30;; Why should you reach for your mouse to get xcalc (calc.exe, gcalc or
25c269ef 31;; whatever), when you have Emacs running already?
d240a249
GM
32;;
33;; If this is not part of your Emacs distribution, then simply bind
34;; `calculator' to a key and make it an autoloaded function, e.g.:
35;; (autoload 'calculator "calculator"
25c269ef 36;; "Run the Emacs calculator." t)
d240a249
GM
37;; (global-set-key [(control return)] 'calculator)
38;;
25c269ef
DL
39;; Written by Eli Barzilay: Maze is Life! eli@barzilay.org
40;; http://www.barzilay.org/
d240a249
GM
41;;
42;; For latest version, check
25c269ef 43;; http://www.barzilay.org/misc/calculator.el
d240a249
GM
44
45(eval-and-compile
46 (if (fboundp 'defgroup) nil
47 (defmacro defgroup (&rest forms) nil)
48 (defmacro defcustom (s v d &rest r) (list 'defvar s v d))))
49
cbb2dddb 50;;;=====================================================================
d240a249
GM
51;;; Customization:
52
53(defgroup calculator nil
25c269ef 54 "Simple Emacs calculator."
d240a249 55 :prefix "calculator"
7def2f92 56 :version "21.1"
d240a249
GM
57 :group 'tools
58 :group 'convenience)
59
60(defcustom calculator-electric-mode nil
61 "*Run `calculator' electrically, in the echo area.
25c269ef
DL
62Electric mode saves some place but changes the way you interact with the
63calculator."
d240a249
GM
64 :type 'boolean
65 :group 'calculator)
66
86f1e1ec
GM
67(defcustom calculator-use-menu t
68 "*Make `calculator' create a menu.
69Note that this requires easymenu. Must be set before loading."
70 :type 'boolean
71 :group 'calculator)
72
d240a249
GM
73(defcustom calculator-bind-escape nil
74 "*If non-nil, set escape to exit the calculator."
75 :type 'boolean
76 :group 'calculator)
77
78(defcustom calculator-unary-style 'postfix
79 "*Value is either 'prefix or 'postfix.
80This determines the default behavior of unary operators."
81 :type '(choice (const prefix) (const postfix))
82 :group 'calculator)
83
25c269ef
DL
84(defcustom calculator-prompt "Calc=%s> "
85 "*The prompt used by the Emacs calculator.
d240a249
GM
86It should contain a \"%s\" somewhere that will indicate the i/o radixes,
87this string will be a two-character string as described in the
88documentation for `calculator-mode'."
89 :type 'string
90 :group 'calculator)
91
25c269ef
DL
92(defcustom calculator-number-digits 3
93 "*The calculator's number of digits used for standard display.
94Used by the `calculator-standard-display' function - it will use the
95format string \"%.NC\" where this number is N and C is a character given
96at runtime."
2826ac81 97 :type 'integer
d240a249
GM
98 :group 'calculator)
99
25c269ef
DL
100(defcustom calculator-remove-zeros t
101 "*Non-nil value means delete all redundant zero decimal digits.
102If this value is not t, and not nil, redundant zeros are removed except
103for one and if it is nil, nothing is removed.
104Used by the `calculator-remove-zeros' function."
105 :type '(choice (const t) (const leave-decimal) (const nil))
d240a249
GM
106 :group 'calculator)
107
25c269ef
DL
108(defcustom calculator-displayer '(std ?n)
109 "*A displayer specification for numerical values.
110This is the displayer used to show all numbers in an expression. Result
111values will be displayed according to the first element of
112`calculator-displayers'.
113
114The displayer is a symbol, a string or an expression. A symbol should
115be the name of a one-argument function, a string is used with a single
116argument and an expression will be evaluated with the variable `num'
117bound to whatever should be displayed. If it is a function symbol, it
118should be able to handle special symbol arguments, currently 'left and
119'right which will be sent by special keys to modify display parameters
120associated with the displayer function (for example to change the number
121of digits displayed).
122
123An exception to the above is the case of the list (std C) where C is a
124character, in this case the `calculator-standard-displayer' function
125will be used with this character for a format string.")
126
127(defcustom calculator-displayers
128 '(((std ?n) "Standard dislpay, decimal point or scientific")
129 (calculator-eng-display "Eng display")
130 ((std ?f) "Standard display, decimal point")
131 ((std ?e) "Standard dislpay, scientific")
132 ("%S" "Emacs printer"))
133 "*A list of displayers.
134Each element is a list of a displayer and a description string. The
135first element is the one which is curently used, this is for the display
136of result values not values in expressions. A displayer specification
137is the same as the values that can be stored in `calculator-displayer'.
138
139`calculator-rotate-displayer' rotates this list."
140 :type 'sexp
d240a249
GM
141 :group 'calculator)
142
25c269ef
DL
143(defcustom calculator-paste-decimals t
144 "*If non-nil, convert pasted integers so they have a decimal point.
145This makes it possible to paste big integers since they will be read as
146floats, otherwise the Emacs reader will fail on them."
d240a249
GM
147 :type 'boolean
148 :group 'calculator)
149
150(defcustom calculator-2s-complement nil
151 "*If non-nil, show negative numbers in 2s complement in radix modes.
152Otherwise show as a negative number."
153 :type 'boolean
154 :group 'calculator)
155
156(defcustom calculator-mode-hook nil
25c269ef 157 "*List of hook functions for `calculator-mode' to run."
d240a249
GM
158 :type 'hook
159 :group 'calculator)
160
161(defcustom calculator-user-registers nil
162 "*An association list of user-defined register bindings.
d240a249
GM
163Each element in this list is a list of a character and a number that
164will be stored in that character's register.
165
166For example, use this to define the golden ratio number:
25c269ef
DL
167 (setq calculator-user-registers '((?g . 1.61803398875)))
168before you load calculator."
d240a249
GM
169 :type '(repeat (cons character number))
170 :set '(lambda (_ val)
171 (and (boundp 'calculator-registers)
172 (setq calculator-registers
173 (append val calculator-registers)))
174 (setq calculator-user-registers val))
175 :group 'calculator)
176
177(defcustom calculator-user-operators nil
178 "*A list of additional operators.
d240a249
GM
179This is a list in the same format as specified in the documentation for
180`calculator-operators', that you can use to bind additional calculator
181operators. It is probably not a good idea to modify this value with
182`customize' since it is too complex...
183
184Examples:
185
7def2f92
DL
186* A very simple one, adding a postfix \"x-to-y\" conversion keys, using
187 t as a prefix key:
d240a249
GM
188
189 (setq calculator-user-operators
190 '((\"tf\" cl-to-fr (+ 32 (/ (* X 9) 5)) 1)
191 (\"tc\" fr-to-cl (/ (* (- X 32) 5) 9) 1)
192 (\"tp\" kg-to-lb (/ X 0.453592) 1)
193 (\"tk\" lb-to-kg (* X 0.453592) 1)
194 (\"tF\" mt-to-ft (/ X 0.3048) 1)
195 (\"tM\" ft-to-mt (* X 0.3048) 1)))
196
197* Using a function-like form is very simple, X for an argument (Y the
198 second in case of a binary operator), TX is a truncated version of X
199 and F does a recursive call, Here is a [very inefficient] Fibonacci
200 number calculation:
201
202 (add-to-list 'calculator-user-operators
203 '(\"F\" fib (if (<= TX 1)
25c269ef
DL
204 1
205 (+ (F (- TX 1)) (F (- TX 2)))) 0))
d240a249
GM
206
207 Note that this will be either postfix or prefix, according to
208 `calculator-unary-style'."
209 :type '(repeat (list string symbol sexp integer integer))
210 :group 'calculator)
211
cbb2dddb 212;;;=====================================================================
d240a249
GM
213;;; Code:
214
cbb2dddb 215;;;---------------------------------------------------------------------
25c269ef
DL
216;;; Variables
217
d240a249 218(defvar calculator-initial-operators
86f1e1ec
GM
219 '(;; "+"/"-" have keybindings of themselves, not calculator-ops
220 ("=" = identity 1 -1)
25c269ef
DL
221 (nobind "+" + + 2 4)
222 (nobind "-" - - 2 4)
223 (nobind "+" + + -1 9)
224 (nobind "-" - - -1 9)
86f1e1ec
GM
225 ("(" \( identity -1 -1)
226 (")" \) identity +1 10)
d240a249
GM
227 ;; normal keys
228 ("|" or (logior TX TY) 2 2)
229 ("#" xor (logxor TX TY) 2 2)
230 ("&" and (logand TX TY) 2 3)
231 ("*" * * 2 5)
232 ("/" / / 2 5)
233 ("\\" div (/ TX TY) 2 5)
234 ("%" rem (% TX TY) 2 5)
235 ("L" log log 2 6)
236 ("S" sin (sin DX) x 6)
237 ("C" cos (cos DX) x 6)
238 ("T" tan (tan DX) x 6)
239 ("IS" asin (D (asin X)) x 6)
240 ("IC" acos (D (acos X)) x 6)
241 ("IT" atan (D (atan X)) x 6)
242 ("Q" sqrt sqrt x 7)
243 ("^" ^ expt 2 7)
244 ("!" ! calculator-fact x 7)
245 (";" 1/ (/ 1 X) 1 7)
246 ("_" - - 1 8)
247 ("~" ~ (lognot TX) x 8)
248 (">" repR calculator-repR 1 8)
249 ("<" repL calculator-repL 1 8)
250 ("v" avg (/ (apply '+ L) (length L)) 0 8)
251 ("l" tot (apply '+ L) 0 8)
252 )
253 "A list of initial operators.
d240a249
GM
254This is a list in the same format as `calculator-operators'. Whenever
255`calculator' starts, it looks at the value of this variable, and if it
256is not empty, its contents is prepended to `calculator-operators' and
257the appropriate key bindings are made.
258
259This variable is then reset to nil. Don't use this if you want to add
260user-defined operators, use `calculator-user-operators' instead.")
261
262(defvar calculator-operators nil
263 "The calculator operators, each a list with:
264
2651. The key that is bound to for this operation (usually a string);
266
2672. The displayed symbol for this function;
268
2693. The function symbol, or a form that uses the variables `X' and `Y',
270 (if it is a binary operator), `TX' and `TY' (truncated integer
271 versions), `DX' (converted to radians if degrees mode is on), `D'
272 (function for converting radians to degrees if deg mode is on), `L'
273 (list of saved values), `F' (function for recursive iteration calls)
274 and evaluates to the function value - these variables are capital;
275
25c269ef
DL
2764. The function's arity, optional, one of: 2 => binary, -1 => prefix
277 unary, +1 => postfix unary, 0 => a 0-arg operator func, non-number =>
278 postfix/prefix as determined by `calculator-unary-style' (the
279 default);
d240a249 280
25c269ef
DL
2815. The function's precedence - should be in the range of 1 (lowest) to
282 9 (highest) (optional, defaults to 1);
d240a249
GM
283
284It it possible have a unary prefix version of a binary operator if it
285comes later in this list. If the list begins with the symbol 'nobind,
286then no key binding will take place - this is only useful for predefined
287keys.
288
289Use `calculator-user-operators' to add operators to this list, see its
290documentation for an example.")
291
292(defvar calculator-stack nil
293 "Stack contents - operations and operands.")
294
295(defvar calculator-curnum nil
296 "Current number being entered (as a string).")
297
298(defvar calculator-stack-display nil
299 "Cons of the stack and its string representation.")
300
301(defvar calculator-char-radix
302 '((?D . nil) (?B . bin) (?O . oct) (?H . hex) (?X . hex))
303 "A table to convert input characters to corresponding radix symbols.")
304
305(defvar calculator-output-radix nil
306 "The mode for display, one of: nil (decimal), 'bin, 'oct or 'hex.")
307
308(defvar calculator-input-radix nil
309 "The mode for input, one of: nil (decimal), 'bin, 'oct or 'hex.")
310
311(defvar calculator-deg nil
312 "Non-nil if trig functions operate on degrees instead of radians.")
313
314(defvar calculator-saved-list nil
315 "A list of saved values collected.")
316
317(defvar calculator-saved-ptr 0
318 "The pointer to the current saved number.")
319
320(defvar calculator-add-saved nil
321 "Bound to t when a value should be added to the saved-list.")
322
323(defvar calculator-display-fragile nil
324 "When non-nil, we see something that the next digit should replace.")
325
326(defvar calculator-buffer nil
327 "The current calculator buffer.")
328
25c269ef
DL
329(defvar calculator-eng-extra nil
330 "Internal value used by `calculator-eng-display'.")
331
332(defvar calculator-eng-tmp-show nil
333 "Internal value used by `calculator-eng-display'.")
334
d240a249
GM
335(defvar calculator-last-opXY nil
336 "The last binary operation and its arguments.
337Used for repeating operations in calculator-repR/L.")
338
339(defvar calculator-registers ; use user-bindings first
340 (append calculator-user-registers (list (cons ?e e) (cons ?p pi)))
341 "The association list of calculator register values.")
342
343(defvar calculator-saved-global-map nil
344 "Saved global key map.")
345
86f1e1ec 346(defvar calculator-restart-other-mode nil
25c269ef
DL
347 "Used to hack restarting with the electric mode changed.")
348
cbb2dddb 349;;;---------------------------------------------------------------------
25c269ef 350;;; Key bindings
86f1e1ec 351
d240a249
GM
352(defvar calculator-mode-map nil
353 "The calculator key map.")
354
355(or calculator-mode-map
86f1e1ec 356 (let ((map (make-sparse-keymap)))
d240a249
GM
357 (suppress-keymap map t)
358 (define-key map "i" nil)
359 (define-key map "o" nil)
86f1e1ec 360 (let ((p
25c269ef
DL
361 '((calculator-open-paren "[")
362 (calculator-close-paren "]")
363 (calculator-op-or-exp "+" "-" [kp-add] [kp-subtract])
364 (calculator-digit "0" "1" "2" "3" "4" "5" "6" "7" "8"
365 "9" "a" "b" "c" "d" "f"
366 [kp-0] [kp-1] [kp-2] [kp-3] [kp-4]
367 [kp-5] [kp-6] [kp-7] [kp-8] [kp-9])
368 (calculator-op [kp-divide] [kp-multiply])
369 (calculator-decimal "." [kp-decimal])
370 (calculator-exp "e")
86f1e1ec
GM
371 (calculator-dec/deg-mode "D")
372 (calculator-set-register "s")
373 (calculator-get-register "g")
374 (calculator-radix-mode "H" "X" "O" "B")
375 (calculator-radix-input-mode "id" "ih" "ix" "io" "ib"
376 "iD" "iH" "iX" "iO" "iB")
377 (calculator-radix-output-mode "od" "oh" "ox" "oo" "ob"
378 "oD" "oH" "oX" "oO" "oB")
25c269ef
DL
379 (calculator-rotate-displayer "'")
380 (calculator-rotate-displayer-back "\"")
381 (calculator-displayer-left "{")
382 (calculator-displayer-right "}")
86f1e1ec
GM
383 (calculator-saved-up [up] [?\C-p])
384 (calculator-saved-down [down] [?\C-n])
385 (calculator-quit "q" [?\C-g])
25c269ef
DL
386 (calculator-enter [enter] [linefeed] [kp-enter]
387 [return] [?\r] [?\n])
86f1e1ec
GM
388 (calculator-save-on-list " " [space])
389 (calculator-clear-saved [?\C-c] [(control delete)])
390 (calculator-save-and-quit [(control return)]
391 [(control kp-enter)])
cbb2dddb
GM
392 (calculator-paste [insert] [(shift insert)]
393 [mouse-2])
86f1e1ec
GM
394 (calculator-clear [delete] [?\C-?] [?\C-d])
395 (calculator-help [?h] [??] [f1] [help])
396 (calculator-copy [(control insert)])
397 (calculator-backspace [backspace])
398 )))
d240a249 399 (while p
86f1e1ec
GM
400 ;; reverse the keys so first defs come last - makes the more
401 ;; sensible bindings visible in the menu
402 (let ((func (car (car p))) (keys (reverse (cdr (car p)))))
403 (while keys
404 (define-key map (car keys) func)
405 (setq keys (cdr keys))))
d240a249
GM
406 (setq p (cdr p))))
407 (if calculator-bind-escape
408 (progn (define-key map [?\e] 'calculator-quit)
409 (define-key map [escape] 'calculator-quit))
410 (define-key map [?\e ?\e ?\e] 'calculator-quit))
411 ;; make C-h work in text-mode
412 (or window-system (define-key map [?\C-h] 'calculator-backspace))
86f1e1ec
GM
413 ;; set up a menu
414 (if (and calculator-use-menu (not (boundp 'calculator-menu)))
415 (let ((radix-selectors
416 (mapcar (lambda (x)
417 `([,(nth 0 x)
418 (calculator-radix-mode ,(nth 2 x))
419 :style radio
420 :keys ,(nth 2 x)
421 :selected
422 (and
423 (eq calculator-input-radix ',(nth 1 x))
424 (eq calculator-output-radix ',(nth 1 x)))]
425 [,(concat (nth 0 x) " Input")
426 (calculator-radix-input-mode ,(nth 2 x))
427 :keys ,(concat "i" (downcase (nth 2 x)))
428 :style radio
429 :selected
430 (eq calculator-input-radix ',(nth 1 x))]
431 [,(concat (nth 0 x) " Output")
432 (calculator-radix-output-mode ,(nth 2 x))
433 :keys ,(concat "o" (downcase (nth 2 x)))
434 :style radio
435 :selected
436 (eq calculator-output-radix ',(nth 1 x))]))
437 '(("Decimal" nil "D")
438 ("Binary" bin "B")
439 ("Octal" oct "O")
440 ("Hexadecimal" hex "H"))))
441 (op '(lambda (name key)
442 `[,name (calculator-op ,key) :keys ,key])))
443 (easy-menu-define
444 calculator-menu map "Calculator menu."
445 `("Calculator"
446 ["Help"
447 (let ((last-command 'calculator-help)) (calculator-help))
448 :keys "?"]
449 "---"
450 ["Copy" calculator-copy]
451 ["Paste" calculator-paste]
452 "---"
453 ["Electric mode"
454 (progn (calculator-quit)
455 (setq calculator-restart-other-mode t)
456 (run-with-timer 0.1 nil '(lambda () (message nil)))
457 ;; the message from the menu will be visible,
458 ;; couldn't make it go away...
459 (calculator))
460 :active (not calculator-electric-mode)]
461 ["Normal mode"
462 (progn (setq calculator-restart-other-mode t)
463 (calculator-quit))
464 :active calculator-electric-mode]
465 "---"
466 ("Functions"
467 ,(funcall op "Repeat-right" ">")
468 ,(funcall op "Repeat-left" "<")
469 "------General------"
470 ,(funcall op "Reciprocal" ";")
471 ,(funcall op "Log" "L")
472 ,(funcall op "Square-root" "Q")
473 ,(funcall op "Factorial" "!")
474 "------Trigonometric------"
475 ,(funcall op "Sinus" "S")
476 ,(funcall op "Cosine" "C")
477 ,(funcall op "Tangent" "T")
478 ,(funcall op "Inv-Sinus" "IS")
479 ,(funcall op "Inv-Cosine" "IC")
480 ,(funcall op "Inv-Tangent" "IT")
481 "------Bitwise------"
482 ,(funcall op "Or" "|")
483 ,(funcall op "Xor" "#")
484 ,(funcall op "And" "&")
485 ,(funcall op "Not" "~"))
486 ("Saved List"
487 ["Eval+Save" calculator-save-on-list]
488 ["Prev number" calculator-saved-up]
489 ["Next number" calculator-saved-down]
490 ["Delete current" calculator-clear
491 :active (and calculator-display-fragile
492 calculator-saved-list
493 (= (car calculator-stack)
494 (nth calculator-saved-ptr
495 calculator-saved-list)))]
496 ["Delete all" calculator-clear-saved]
497 "---"
498 ,(funcall op "List-total" "l")
499 ,(funcall op "List-average" "v"))
500 ("Registers"
501 ["Get register" calculator-get-register]
502 ["Set register" calculator-set-register])
503 ("Modes"
504 ["Radians"
505 (progn
506 (and (or calculator-input-radix calculator-output-radix)
507 (calculator-radix-mode "D"))
508 (and calculator-deg (calculator-dec/deg-mode)))
509 :keys "D"
510 :style radio
511 :selected (not (or calculator-input-radix
512 calculator-output-radix
513 calculator-deg))]
514 ["Degrees"
515 (progn
516 (and (or calculator-input-radix calculator-output-radix)
517 (calculator-radix-mode "D"))
518 (or calculator-deg (calculator-dec/deg-mode)))
519 :keys "D"
520 :style radio
521 :selected (and calculator-deg
522 (not (or calculator-input-radix
523 calculator-output-radix)))]
524 "---"
525 ,@(mapcar 'car radix-selectors)
526 ("Seperate I/O"
527 ,@(mapcar (lambda (x) (nth 1 x)) radix-selectors)
528 "---"
529 ,@(mapcar (lambda (x) (nth 2 x)) radix-selectors)))
25c269ef
DL
530 ("Decimal Dislpay"
531 ,@(mapcar (lambda (d)
532 (vector (cadr d)
533 ;; Note: inserts actual object here
534 `(calculator-rotate-displayer ',d)))
535 calculator-displayers)
536 "---"
537 ["Change Display Left" calculator-displayer-left]
538 ["Change Display Right" calculator-displayer-right])
86f1e1ec
GM
539 "---"
540 ["Copy+Quit" calculator-save-and-quit]
541 ["Quit" calculator-quit]))))
d240a249
GM
542 (setq calculator-mode-map map)))
543
cbb2dddb 544;;;---------------------------------------------------------------------
25c269ef
DL
545;;; Startup and mode stuff
546
d240a249 547(defun calculator-mode ()
25c269ef
DL
548 ;; this help is also used as the major help screen
549 "A [not so] simple calculator for Emacs.
d240a249
GM
550
551This calculator is used in the same way as other popular calculators
552like xcalc or calc.exe - but using an Emacs interface.
553
554Expressions are entered using normal infix notation, parens are used as
555normal. Unary functions are usually postfix, but some depends on the
556value of `calculator-unary-style' (if the style for an operator below is
557specified, then it is fixed, otherwise it depends on this variable).
558`+' and `-' can be used as either binary operators or prefix unary
559operators. Numbers can be entered with exponential notation using `e',
560except when using a non-decimal radix mode for input (in this case `e'
561will be the hexadecimal digit).
562
563Here are the editing keys:
564* `RET' `=' evaluate the current expression
565* `C-insert' copy the whole current expression to the `kill-ring'
86f1e1ec 566* `C-return' evaluate, save result the `kill-ring' and exit
d240a249
GM
567* `insert' paste a number if the one was copied (normally)
568* `delete' `C-d' clear last argument or whole expression (hit twice)
569* `backspace' delete a digit or a previous expression element
570* `h' `?' pop-up a quick reference help
571* `ESC' `q' exit (`ESC' can be used if `calculator-bind-escape' is
572 non-nil, otherwise use three consecutive `ESC's)
573
574These operators are pre-defined:
575* `+' `-' `*' `/' the common binary operators
576* `\\' `%' integer division and reminder
577* `_' `;' postfix unary negation and reciprocal
578* `^' `L' binary operators for x^y and log(x) in base y
579* `Q' `!' unary square root and factorial
580* `S' `C' `T' unary trigonometric operators - sin, cos and tan
581* `|' `#' `&' `~' bitwise operators - or, xor, and, not
582
583The trigonometric functions can be inverted if prefixed with an `I', see
584below for the way to use degrees instead of the default radians.
585
586Two special postfix unary operators are `>' and `<': whenever a binary
587operator is performed, it is remembered along with its arguments; then
588`>' (`<') will apply the same operator with the same right (left)
589argument.
590
591hex/oct/bin modes can be set for input and for display separately.
592Another toggle-able mode is for using degrees instead of radians for
593trigonometric functions.
594The keys to switch modes are (`X' is shortcut for `H'):
595* `D' switch to all-decimal mode, or toggle degrees/radians
596* `B' `O' `H' `X' binary/octal/hexadecimal modes for input & display
597* `i' `o' followed by one of `D' `B' `O' `H' `X' (case
598 insensitive) sets only the input or display radix mode
599The prompt indicates the current modes:
600* \"D=\": degrees mode;
601* \"?=\": (? is B/O/H) this is the radix for both input and output;
602* \"=?\": (? is B/O/H) the display radix (when input is decimal);
603* \"??\": (? is D/B/O/H) 1st char for input radix, 2nd for display.
604
25c269ef
DL
605Also, the quote character can be used to switch display modes for
606decimal numbers (double-quote rotates back), and the two brace
607characters (\"{\" and \"}\" change display parameters that these
608displayers use (if they handle such).
609
d240a249
GM
610Values can be saved for future reference in either a list of saved
611values, or in registers.
612
613The list of saved values is useful for statistics operations on some
614collected data. It is possible to navigate in this list, and if the
615value shown is the current one on the list, an indication is displayed
616as \"[N]\" if this is the last number and there are N numbers, or
617\"[M/N]\" if the M-th value is shown.
618* `SPC' evaluate the current value as usual, but also adds
619 the result to the list of saved values
620* `l' `v' computes total / average of saved values
621* `up' `C-p' browse to the previous value in the list
622* `down' `C-n' browse to the next value in the list
623* `delete' `C-d' remove current value from the list (if it is on it)
624* `C-delete' `C-c' delete the whole list
625
626Registers are variable-like place-holders for values:
627* `s' followed by a character attach the current value to that character
628* `g' followed by a character fetches the attached value
629
630There are many variables that can be used to customize the calculator.
631Some interesting customization variables are:
632* `calculator-electric-mode' use only the echo-area electrically.
633* `calculator-unary-style' set most unary ops to pre/postfix style.
634* `calculator-user-registers' to define user-preset registers.
635* `calculator-user-operators' to add user-defined operators.
636See the documentation for these variables, and \"calculator.el\" for
637more information.
638
639\\{calculator-mode-map}"
640 (interactive)
641 (kill-all-local-variables)
642 (setq major-mode 'calculator-mode)
643 (setq mode-name "Calculator")
644 (use-local-map calculator-mode-map)
645 (run-hooks 'calculator-mode-hook))
646
25c269ef
DL
647(eval-when-compile (require 'electric) (require 'ehelp))
648
d240a249
GM
649;;;###autoload
650(defun calculator ()
25c269ef 651 "Run the Emacs calculator.
d240a249
GM
652See the documentation for `calculator-mode' for more information."
653 (interactive)
86f1e1ec
GM
654 (if calculator-restart-other-mode
655 (setq calculator-electric-mode (not calculator-electric-mode)))
d240a249
GM
656 (if calculator-initial-operators
657 (progn (calculator-add-operators calculator-initial-operators)
658 (setq calculator-initial-operators nil)
659 ;; don't change this since it is a customization variable,
25c269ef 660 ;; its set function will add any new operators
d240a249 661 (calculator-add-operators calculator-user-operators)))
d240a249
GM
662 (if calculator-electric-mode
663 (save-window-excursion
86f1e1ec 664 (progn (require 'electric) (message nil)) ; hide load message
d240a249
GM
665 (let (old-g-map old-l-map (echo-keystrokes 0)
666 (garbage-collection-messages nil)) ; no gc msg when electric
d240a249
GM
667 ;; strange behavior in FSF: doesn't always select correct
668 ;; minibuffer. I have no idea how to fix this
669 (setq calculator-buffer (window-buffer (minibuffer-window)))
670 (select-window (minibuffer-window))
671 (calculator-reset)
672 (calculator-update-display)
673 (setq old-l-map (current-local-map))
674 (setq old-g-map (current-global-map))
675 (setq calculator-saved-global-map (current-global-map))
86f1e1ec 676 (use-local-map nil)
d240a249
GM
677 (use-global-map calculator-mode-map)
678 (unwind-protect
679 (catch 'calculator-done
680 (Electric-command-loop
681 'calculator-done
682 ;; can't use 'noprompt, bug in electric.el
683 '(lambda () 'noprompt)
684 nil
86f1e1ec 685 (lambda (x y) (calculator-update-display))))
d240a249
GM
686 (and calculator-buffer
687 (catch 'calculator-done (calculator-quit)))
688 (use-local-map old-l-map)
689 (use-global-map old-g-map))))
86f1e1ec
GM
690 (progn
691 (setq calculator-buffer
692 (or (and (bufferp calculator-buffer)
693 (buffer-live-p calculator-buffer)
694 calculator-buffer)
695 (if calculator-electric-mode
696 (get-buffer-create "*calculator*")
697 (let ((split-window-keep-point nil)
698 (window-min-height 2))
699 (select-window
25c269ef
DL
700 ;; maybe leave two lines for our window because
701 ;; of the normal `raised' modeline in Emacs 21
702 (split-window-vertically
703 (- (window-height)
704 (if (and
705 (fboundp 'face-attr-construct)
706 (plist-get (face-attr-construct 'modeline)
707 :box))
708 3
709 2))))
86f1e1ec
GM
710 (switch-to-buffer
711 (get-buffer-create "*calculator*"))))))
712 (set-buffer calculator-buffer)
713 (calculator-mode)
714 (setq buffer-read-only t)
715 (calculator-reset)
716 (message "Hit `?' For a quick help screen.")))
717 (if (and calculator-restart-other-mode calculator-electric-mode)
718 (calculator)))
d240a249 719
cbb2dddb 720;;;---------------------------------------------------------------------
25c269ef
DL
721;;; Operatos
722
d240a249
GM
723(defun calculator-op-arity (op)
724 "Return OP's arity, 2, +1 or -1."
725 (let ((arity (or (nth 3 op) 'x)))
726 (if (numberp arity)
727 arity
728 (if (eq calculator-unary-style 'postfix) +1 -1))))
729
730(defun calculator-op-prec (op)
731 "Return OP's precedence for reducing when inserting into the stack.
732Defaults to 1."
733 (or (nth 4 op) 1))
734
735(defun calculator-add-operators (more-ops)
736 "This function handles operator addition.
737Adds MORE-OPS to `calculator-operator', called initially to handle
738`calculator-initial-operators' and `calculator-user-operators'."
739 (let ((added-ops nil))
740 (while more-ops
741 (or (eq (car (car more-ops)) 'nobind)
742 (let ((i -1) (key (car (car more-ops))))
743 ;; make sure the key is undefined, so it's easy to define
744 ;; prefix keys
745 (while (< (setq i (1+ i)) (length key))
746 (or (keymapp
747 (lookup-key calculator-mode-map
748 (substring key 0 (1+ i))))
749 (progn
750 (define-key
751 calculator-mode-map (substring key 0 (1+ i)) nil)
752 (setq i (length key)))))
753 (define-key calculator-mode-map key 'calculator-op)))
754 (setq added-ops (cons (if (eq (car (car more-ops)) 'nobind)
755 (cdr (car more-ops))
756 (car more-ops))
757 added-ops))
758 (setq more-ops (cdr more-ops)))
759 ;; added-ops come first, but in correct order
760 (setq calculator-operators
761 (append (nreverse added-ops) calculator-operators))))
762
cbb2dddb 763;;;---------------------------------------------------------------------
25c269ef
DL
764;;; Display stuff
765
d240a249
GM
766(defun calculator-reset ()
767 "Reset calculator variables."
86f1e1ec
GM
768 (or calculator-restart-other-mode
769 (setq calculator-stack nil
770 calculator-curnum nil
771 calculator-stack-display nil
772 calculator-display-fragile nil))
773 (setq calculator-restart-other-mode nil)
d240a249
GM
774 (calculator-update-display))
775
776(defun calculator-get-prompt ()
777 "Return a string to display.
778The string is set not to exceed the screen width."
779 (let* ((calculator-prompt
780 (format calculator-prompt
781 (cond
782 ((or calculator-output-radix calculator-input-radix)
783 (if (eq calculator-output-radix
784 calculator-input-radix)
785 (concat
786 (char-to-string
787 (car (rassq calculator-output-radix
788 calculator-char-radix)))
789 "=")
790 (concat
791 (if calculator-input-radix
792 (char-to-string
793 (car (rassq calculator-input-radix
794 calculator-char-radix)))
795 "=")
796 (char-to-string
797 (car (rassq calculator-output-radix
798 calculator-char-radix))))))
799 (calculator-deg "D=")
800 (t "=="))))
801 (prompt
802 (concat calculator-prompt
803 (cdr calculator-stack-display)
804 (cond (calculator-curnum
805 ;; number being typed
806 (concat calculator-curnum "_"))
807 ((and (= 1 (length calculator-stack))
808 calculator-display-fragile)
809 ;; only the result is shown, next number will
810 ;; restart
811 nil)
812 (t
813 ;; waiting for a number or an operator
814 "?"))))
815 (trim (- (length prompt) (1- (window-width)))))
816 (if (<= trim 0)
817 prompt
818 (concat calculator-prompt
819 (substring prompt (+ trim (length calculator-prompt)))))))
820
821(defun calculator-curnum-value ()
822 "Get the numeric value of the displayed number string as a float."
823 (if calculator-input-radix
824 (let ((radix
825 (cdr (assq calculator-input-radix
826 '((bin . 2) (oct . 8) (hex . 16)))))
827 (i -1) (value 0))
828 ;; assume valid input (upcased & characters in range)
829 (while (< (setq i (1+ i)) (length calculator-curnum))
830 (setq value
831 (+ (let ((ch (aref calculator-curnum i)))
832 (- ch (if (<= ch ?9) ?0 (- ?A 10))))
833 (* radix value))))
834 value)
835 (car
836 (read-from-string
837 (cond
838 ((equal "." calculator-curnum)
839 "0.0")
840 ((string-match "[eE][+-]?$" calculator-curnum)
841 (concat calculator-curnum "0"))
842 ((string-match "\\.[0-9]\\|[eE]" calculator-curnum)
843 calculator-curnum)
844 ((string-match "\\." calculator-curnum)
25c269ef 845 ;; do this because Emacs reads "23." as an integer
d240a249
GM
846 (concat calculator-curnum "0"))
847 ((stringp calculator-curnum)
848 (concat calculator-curnum ".0"))
849 (t "0.0"))))))
850
25c269ef
DL
851(defun calculator-rotate-displayer (&optional new-disp)
852 "Switch to the next displayer on the `calculator-displayers' list.
853Can be called with an optional argument NEW-DISP to force rotation to
854that argument."
855 (interactive)
856 (setq calculator-displayers
857 (if (and new-disp (memq new-disp calculator-displayers))
858 (let ((tmp nil))
859 (while (not (eq (car calculator-displayers) new-disp))
860 (setq tmp (cons (car calculator-displayers) tmp))
861 (setq calculator-displayers (cdr calculator-displayers)))
862 (setq calculator-displayers
863 (nconc calculator-displayers (nreverse tmp))))
864 (nconc (cdr calculator-displayers)
865 (list (car calculator-displayers)))))
866 (message "Using %s." (cadr (car calculator-displayers)))
867 (if calculator-electric-mode
868 (progn (sit-for 1) (message nil)))
869 (calculator-enter))
870
871(defun calculator-rotate-displayer-back ()
872 "Like `calculator-rotate-displayer', but rotates modes back."
873 (interactive)
874 (calculator-rotate-displayer (car (last calculator-displayers))))
875
876(defun calculator-displayer-left ()
877 "Send the current displayer function a 'left argument.
878This is used to modify display arguments (if the current displayer
879function supports this)."
880 (interactive)
881 (and (car calculator-displayers)
882 (let ((disp (caar calculator-displayers)))
883 (cond ((symbolp disp) (funcall disp 'left))
884 ((and (consp disp) (eq 'std (car disp)))
885 (calculator-standard-displayer 'left (cadr disp)))))))
886
887(defun calculator-displayer-right ()
888 "Send the current displayer function a 'right argument.
889This is used to modify display arguments (if the current displayer
890function supports this)."
891 (interactive)
892 (and (car calculator-displayers)
893 (let ((disp (caar calculator-displayers)))
894 (cond ((symbolp disp) (funcall disp 'right))
895 ((and (consp disp) (eq 'std (car disp)))
896 (calculator-standard-displayer 'right (cadr disp)))))))
897
898(defun calculator-remove-zeros (numstr)
899 "Get a number string NUMSTR and remove unnecessary zeroes.
900the behavior of this function is controlled by
901`calculator-remove-zeros'."
902 (cond ((and (eq calculator-remove-zeros t)
903 (string-match "\\.0+\\([eE][+-]?[0-9]*\\)?$" numstr))
904 ;; remove all redundant zeros leaving an integer
905 (if (match-beginning 1)
906 (concat (substring numstr 0 (match-beginning 0))
907 (match-string 1 numstr))
908 (substring numstr 0 (match-beginning 0))))
909 ((and calculator-remove-zeros
910 (string-match
911 "\\..\\([0-9]*[1-9]\\)?\\(0+\\)\\([eE][+-]?[0-9]*\\)?$"
912 numstr))
913 ;; remove zeros, except for first after the "."
914 (if (match-beginning 3)
915 (concat (substring numstr 0 (match-beginning 2))
916 (match-string 3 numstr))
917 (substring numstr 0 (match-beginning 2))))
918 (t numstr)))
919
920(defun calculator-standard-displayer (num char)
921 "Standard display function, used to display NUM.
922Its behavior is determined by `calculator-number-digits' and the given
923CHAR argument (both will be used to compose a format string). If the
924char is \"n\" then this function will choose one between %f or %e, this
925is a work around %g jumping to exponential notation too fast.
926
927The special 'left and 'right symbols will make it change the current
928number of digits displayed (`calculator-number-digits').
929
930It will also remove redundant zeros from the result."
931 (if (symbolp num)
932 (cond ((eq num 'left)
933 (and (> calculator-number-digits 0)
934 (setq calculator-number-digits
935 (1- calculator-number-digits))
936 (calculator-enter)))
937 ((eq num 'right)
938 (setq calculator-number-digits
939 (1+ calculator-number-digits))
940 (calculator-enter)))
941 (let ((str (format
942 (concat "%."
943 (number-to-string calculator-number-digits)
944 (if (eq char ?n)
945 (let ((n (abs num)))
946 (if (or (< n 0.001) (> n 1e8)) "e" "f"))
947 (string char)))
948 num)))
949 (calculator-remove-zeros str))))
950
951(defun calculator-eng-display (num)
952 "Display NUM in engineering notation.
953The number of decimal digits used is controlled by
954`calculator-number-digits', so to change it at runtime you have to use
955the 'left or 'right when one of the standard modes is used."
956 (if (symbolp num)
957 (cond ((eq num 'left)
958 (setq calculator-eng-extra
959 (if calculator-eng-extra
960 (1+ calculator-eng-extra)
961 1))
962 (let ((calculator-eng-tmp-show t)) (calculator-enter)))
963 ((eq num 'right)
964 (setq calculator-eng-extra
965 (if calculator-eng-extra
966 (1- calculator-eng-extra)
967 -1))
968 (let ((calculator-eng-tmp-show t)) (calculator-enter))))
969 (let ((exp 0))
970 (and (not (= 0 num))
971 (progn
972 (while (< (abs num) 1.0)
973 (setq num (* num 1000.0)) (setq exp (- exp 3)))
974 (while (> (abs num) 999.0)
975 (setq num (/ num 1000.0)) (setq exp (+ exp 3)))
976 (and calculator-eng-tmp-show
977 (not (= 0 calculator-eng-extra))
978 (let ((i calculator-eng-extra))
979 (while (> i 0)
980 (setq num (* num 1000.0)) (setq exp (- exp 3))
981 (setq i (1- i)))
982 (while (< i 0)
983 (setq num (/ num 1000.0)) (setq exp (+ exp 3))
984 (setq i (1+ i)))))))
985 (or calculator-eng-tmp-show (setq calculator-eng-extra nil))
986 (let ((str (format (concat "%." calculator-number-digits "f")
987 num)))
988 (concat (let ((calculator-remove-zeros
989 ;; make sure we don't leave integers
990 (and calculator-remove-zeros 'x)))
991 (calculator-remove-zeros str))
992 "e" (number-to-string exp))))))
993
d240a249
GM
994(defun calculator-num-to-string (num)
995 "Convert NUM to a displayable string."
996 (cond
997 ((and (numberp num) calculator-output-radix)
998 ;; print with radix - for binary I convert the octal number
999 (let ((str (format (if (eq calculator-output-radix 'hex) "%x" "%o")
1000 (calculator-truncate
1001 (if calculator-2s-complement num (abs num))))))
1002 (if (eq calculator-output-radix 'bin)
1003 (let ((i -1) (s ""))
1004 (while (< (setq i (1+ i)) (length str))
1005 (setq s
1006 (concat s
1007 (cdr (assq (aref str i)
1008 '((?0 . "000") (?1 . "001")
1009 (?2 . "010") (?3 . "011")
1010 (?4 . "100") (?5 . "101")
1011 (?6 . "110") (?7 . "111")))))))
1012 (string-match "^0*\\(.+\\)" s)
1013 (setq str (match-string 1 s))))
1014 (upcase
1015 (if (and (not calculator-2s-complement) (< num 0))
1016 (concat "-" str)
1017 str))))
25c269ef
DL
1018 ((and (numberp num) (car calculator-displayers))
1019 (let ((disp (if (= 1 (length calculator-stack))
1020 ;; customizable display for a single value
1021 (caar calculator-displayers)
1022 calculator-displayer)))
1023 (cond ((stringp disp) (format disp num))
1024 ((symbolp disp) (funcall disp num))
1025 ((and (consp disp)
1026 (eq 'std (car disp)))
1027 (calculator-standard-displayer
1028 num (cadr disp)))
1029 ((listp disp) (eval disp))
1030 (t (prin1-to-string num t)))))
1031 ;; operators are printed here
d240a249
GM
1032 (t (prin1-to-string (nth 1 num) t))))
1033
1034(defun calculator-update-display (&optional force)
1035 "Update the display.
1036If optional argument FORCE is non-nil, don't use the cached string."
1037 (set-buffer calculator-buffer)
1038 ;; update calculator-stack-display
1039 (if (or force
1040 (not (eq (car calculator-stack-display) calculator-stack)))
1041 (setq calculator-stack-display
1042 (cons calculator-stack
1043 (if calculator-stack
1044 (concat
1045 (mapconcat 'calculator-num-to-string
1046 (reverse calculator-stack)
1047 " ")
1048 " "
1049 (and calculator-display-fragile
1050 calculator-saved-list
1051 (= (car calculator-stack)
1052 (nth calculator-saved-ptr
1053 calculator-saved-list))
1054 (if (= 0 calculator-saved-ptr)
1055 (format "[%s]" (length calculator-saved-list))
1056 (format "[%s/%s]"
1057 (- (length calculator-saved-list)
1058 calculator-saved-ptr)
1059 (length calculator-saved-list)))))
1060 ""))))
1061 (let ((inhibit-read-only t))
1062 (erase-buffer)
1063 (insert (calculator-get-prompt)))
1064 (set-buffer-modified-p nil)
1065 (if calculator-display-fragile
1066 (goto-char (1+ (length calculator-prompt)))
1067 (goto-char (1- (point)))))
1068
cbb2dddb 1069;;;---------------------------------------------------------------------
25c269ef
DL
1070;;; Stack computations
1071
d240a249
GM
1072(defun calculator-reduce-stack (prec)
1073 "Reduce the stack using top operator.
1074PREC is a precedence - reduce everything with higher precedence."
1075 (while
1076 (cond
1077 ((and (cdr (cdr calculator-stack)) ; have three values
1078 (consp (nth 0 calculator-stack)) ; two operators & num
1079 (numberp (nth 1 calculator-stack))
1080 (consp (nth 2 calculator-stack))
1081 (eq '\) (nth 1 (nth 0 calculator-stack)))
1082 (eq '\( (nth 1 (nth 2 calculator-stack))))
1083 ;; reduce "... ( x )" --> "... x"
1084 (setq calculator-stack
1085 (cons (nth 1 calculator-stack)
1086 (nthcdr 3 calculator-stack)))
1087 ;; another iteration
1088 t)
1089 ((and (cdr (cdr calculator-stack)) ; have three values
1090 (numberp (nth 0 calculator-stack)) ; two nums & operator
1091 (consp (nth 1 calculator-stack))
1092 (numberp (nth 2 calculator-stack))
1093 (= 2 (calculator-op-arity ; binary operator
1094 (nth 1 calculator-stack)))
1095 (<= prec ; with higher prec.
1096 (calculator-op-prec (nth 1 calculator-stack))))
1097 ;; reduce "... x op y" --> "... r", r is the result
1098 (setq calculator-stack
1099 (cons (calculator-funcall
1100 (nth 2 (nth 1 calculator-stack))
1101 (nth 2 calculator-stack)
1102 (nth 0 calculator-stack))
1103 (nthcdr 3 calculator-stack)))
1104 ;; another iteration
1105 t)
1106 ((and (>= (length calculator-stack) 2) ; have two values
1107 (numberp (nth 0 calculator-stack)) ; number & operator
1108 (consp (nth 1 calculator-stack))
1109 (= -1 (calculator-op-arity ; prefix-unary op
1110 (nth 1 calculator-stack)))
1111 (<= prec ; with higher prec.
1112 (calculator-op-prec (nth 1 calculator-stack))))
1113 ;; reduce "... op x" --> "... r" for prefix op
1114 (setq calculator-stack
1115 (cons (calculator-funcall
1116 (nth 2 (nth 1 calculator-stack))
1117 (nth 0 calculator-stack))
1118 (nthcdr 2 calculator-stack)))
1119 ;; another iteration
1120 t)
1121 ((and (cdr calculator-stack) ; have two values
1122 (consp (nth 0 calculator-stack)) ; operator & number
1123 (numberp (nth 1 calculator-stack))
1124 (= +1 (calculator-op-arity ; postfix-unary op
1125 (nth 0 calculator-stack)))
1126 (<= prec ; with higher prec.
1127 (calculator-op-prec (nth 0 calculator-stack))))
1128 ;; reduce "... x op" --> "... r" for postfix op
1129 (setq calculator-stack
1130 (cons (calculator-funcall
1131 (nth 2 (nth 0 calculator-stack))
1132 (nth 1 calculator-stack))
1133 (nthcdr 2 calculator-stack)))
1134 ;; another iteration
1135 t)
1136 ((and calculator-stack ; have one value
1137 (consp (nth 0 calculator-stack)) ; an operator
1138 (= 0 (calculator-op-arity ; 0-ary op
1139 (nth 0 calculator-stack))))
1140 ;; reduce "... op" --> "... r" for 0-ary op
1141 (setq calculator-stack
1142 (cons (calculator-funcall
1143 (nth 2 (nth 0 calculator-stack)))
1144 (nthcdr 1 calculator-stack)))
1145 ;; another iteration
1146 t)
1147 ((and (cdr calculator-stack) ; have two values
1148 (numberp (nth 0 calculator-stack)) ; both numbers
1149 (numberp (nth 1 calculator-stack)))
1150 ;; get rid of redundant numbers:
1151 ;; reduce "... y x" --> "... x"
1152 ;; needed for 0-ary ops that puts more values
1153 (setcdr calculator-stack (cdr (cdr calculator-stack))))
1154 (t ;; no more iterations
1155 nil))))
1156
25c269ef
DL
1157(defun calculator-funcall (f &optional X Y)
1158 "If F is a symbol, evaluate (F X Y).
1159Otherwise, it should be a list, evaluate it with X, Y bound to the
1160arguments."
1161 ;; remember binary ops for calculator-repR/L
1162 (if Y (setq calculator-last-opXY (list f X Y)))
1163 (condition-case nil
1164 ;; there used to be code here that returns 0 if the result was
1165 ;; smaller than calculator-epsilon (1e-15). I don't think this is
1166 ;; necessary now.
1167 (if (symbolp f)
1168 (cond ((and X Y) (funcall f X Y))
1169 (X (funcall f X))
1170 (t (funcall f)))
1171 ;; f is an expression
1172 (let* ((__f__ f) ; so we can get this value below...
1173 (TX (calculator-truncate X))
1174 (TY (and Y (calculator-truncate Y)))
1175 (DX (if calculator-deg (/ (* X pi) 180) X))
1176 (L calculator-saved-list)
1177 (Fbound (fboundp 'F))
1178 (Fsave (and Fbound (symbol-function 'F)))
1179 (Dbound (fboundp 'D))
1180 (Dsave (and Dbound (symbol-function 'D))))
1181 ;; a shortened version of flet
1182 (fset 'F (function
1183 (lambda (&optional x y)
1184 (calculator-funcall __f__ x y))))
1185 (fset 'D (function
1186 (lambda (x)
1187 (if calculator-deg (/ (* x 180) pi) x))))
1188 (unwind-protect (eval f)
1189 (if Fbound (fset 'F Fsave) (fmakunbound 'F))
1190 (if Dbound (fset 'D Dsave) (fmakunbound 'D)))))
1191 (error 0)))
1192
d240a249
GM
1193(eval-when-compile ; silence the compiler
1194 (or (fboundp 'event-key)
1195 (defun event-key (&rest _) nil))
1196 (or (fboundp 'key-press-event-p)
1197 (defun key-press-event-p (&rest _) nil)))
1198
cbb2dddb 1199;;;---------------------------------------------------------------------
25c269ef
DL
1200;;; Input interaction
1201
86f1e1ec
GM
1202(defun calculator-last-input (&optional keys)
1203 "Last char (or event or event sequence) that was read.
1204Optional string argument KEYS will force using it as the keys entered."
1205 (let ((inp (or keys (this-command-keys))))
d240a249
GM
1206 (if (or (stringp inp) (not (arrayp inp)))
1207 inp
1208 ;; this translates kp-x to x and [tries to] create a string to
1209 ;; lookup operators
1210 (let* ((i -1) (converted-str (make-string (length inp) ? )) k)
1211 ;; converts an array to a string the ops lookup with keypad
1212 ;; input
1213 (while (< (setq i (1+ i)) (length inp))
1214 (setq k (aref inp i))
1215 ;; if Emacs will someday have a event-key, then this would
1216 ;; probably be modified anyway
1217 (and (fboundp 'event-key) (key-press-event-p k)
25c269ef 1218 (event-key k) (setq k (event-key k)))
d240a249
GM
1219 ;; assume all symbols are translatable with an ascii-character
1220 (and (symbolp k)
1221 (setq k (or (get k 'ascii-character) ? )))
1222 (aset converted-str i k))
1223 converted-str))))
1224
1225(defun calculator-clear-fragile (&optional op)
1226 "Clear the fragile flag if it was set, then maybe reset all.
1227OP is the operator (if any) that caused this call."
1228 (if (and calculator-display-fragile
1229 (or (not op)
1230 (= -1 (calculator-op-arity op))
1231 (= 0 (calculator-op-arity op))))
1232 ;; reset if last calc finished, and now get a num or prefix or 0-ary
25c269ef 1233 ;; op
d240a249
GM
1234 (calculator-reset))
1235 (setq calculator-display-fragile nil))
1236
1237(defun calculator-digit ()
1238 "Enter a single digit."
1239 (interactive)
1240 (let ((inp (aref (calculator-last-input) 0)))
1241 (if (and (or calculator-display-fragile
1242 (not (numberp (car calculator-stack))))
1243 (cond
1244 ((not calculator-input-radix) (<= inp ?9))
1245 ((eq calculator-input-radix 'bin) (<= inp ?1))
1246 ((eq calculator-input-radix 'oct) (<= inp ?7))
1247 (t t)))
1248 ;; enter digit if starting a new computation or have an op on the
25c269ef 1249 ;; stack
d240a249
GM
1250 (progn
1251 (calculator-clear-fragile)
1252 (let ((digit (upcase (char-to-string inp))))
1253 (if (equal calculator-curnum "0")
1254 (setq calculator-curnum nil))
1255 (setq calculator-curnum
1256 (concat (or calculator-curnum "") digit)))
1257 (calculator-update-display)))))
1258
1259(defun calculator-decimal ()
1260 "Enter a decimal period."
1261 (interactive)
1262 (if (and (not calculator-input-radix)
1263 (or calculator-display-fragile
1264 (not (numberp (car calculator-stack))))
1265 (not (and calculator-curnum
1266 (string-match "[.eE]" calculator-curnum))))
1267 ;; enter the period on the same condition as a digit, only if no
25c269ef 1268 ;; period or exponent entered yet
d240a249
GM
1269 (progn
1270 (calculator-clear-fragile)
1271 (setq calculator-curnum (concat (or calculator-curnum "0") "."))
1272 (calculator-update-display))))
1273
1274(defun calculator-exp ()
1275 "Enter an `E' exponent character, or a digit in hex input mode."
1276 (interactive)
1277 (if calculator-input-radix
1278 (calculator-digit)
1279 (if (and (or calculator-display-fragile
1280 (not (numberp (car calculator-stack))))
1281 (not (and calculator-curnum
1282 (string-match "[eE]" calculator-curnum))))
25c269ef 1283 ;; same condition as above, also no E so far
d240a249
GM
1284 (progn
1285 (calculator-clear-fragile)
1286 (setq calculator-curnum (concat (or calculator-curnum "1") "e"))
1287 (calculator-update-display)))))
1288
86f1e1ec
GM
1289(defun calculator-op (&optional keys)
1290 "Enter an operator on the stack, doing all necessary reductions.
1291Optional string argument KEYS will force using it as the keys entered."
d240a249 1292 (interactive)
25c269ef
DL
1293 (catch 'op-error
1294 (let* ((last-inp (calculator-last-input keys))
1295 (op (assoc last-inp calculator-operators)))
1296 (calculator-clear-fragile op)
1297 (if (and calculator-curnum (/= (calculator-op-arity op) 0))
1298 (setq calculator-stack
1299 (cons (calculator-curnum-value) calculator-stack)))
1300 (setq calculator-curnum nil)
1301 (if (and (= 2 (calculator-op-arity op))
1302 (not (and calculator-stack
1303 (numberp (nth 0 calculator-stack)))))
1304 ;; we have a binary operator but no number - search for a prefix
1305 ;; version
1306 (let ((rest-ops calculator-operators))
1307 (while (not (equal last-inp (car (car rest-ops))))
1308 (setq rest-ops (cdr rest-ops)))
1309 (setq op (assoc last-inp (cdr rest-ops)))
1310 (if (not (and op (= -1 (calculator-op-arity op))))
1311 ;;(error "Binary operator without a first operand")
1312 (progn
1313 (message "Binary operator without a first operand")
1314 (if calculator-electric-mode
1315 (progn (sit-for 1) (message nil)))
1316 (throw 'op-error nil)))))
1317 (calculator-reduce-stack
1318 (cond ((eq (nth 1 op) '\() 10)
1319 ((eq (nth 1 op) '\)) 0)
1320 (t (calculator-op-prec op))))
1321 (if (or (and (= -1 (calculator-op-arity op))
1322 (numberp (car calculator-stack)))
1323 (and (/= (calculator-op-arity op) -1)
1324 (/= (calculator-op-arity op) 0)
1325 (not (numberp (car calculator-stack)))))
1326 ;;(error "Unterminated expression")
1327 (progn
1328 (message "Unterminated expression")
1329 (if calculator-electric-mode
1330 (progn (sit-for 1) (message nil)))
1331 (throw 'op-error nil)))
1332 (setq calculator-stack (cons op calculator-stack))
1333 (calculator-reduce-stack (calculator-op-prec op))
1334 (and (= (length calculator-stack) 1)
1335 (numberp (nth 0 calculator-stack))
1336 ;; the display is fragile if it contains only one number
1337 (setq calculator-display-fragile t)
1338 ;; add number to the saved-list
1339 calculator-add-saved
1340 (if (= 0 calculator-saved-ptr)
1341 (setq calculator-saved-list
1342 (cons (car calculator-stack) calculator-saved-list))
1343 (let ((p (nthcdr (1- calculator-saved-ptr)
1344 calculator-saved-list)))
1345 (setcdr p (cons (car calculator-stack) (cdr p))))))
1346 (calculator-update-display))))
d240a249
GM
1347
1348(defun calculator-op-or-exp ()
1349 "Either enter an operator or a digit.
25c269ef
DL
1350Used with +/- for entering them as digits in numbers like 1e-3 (there is
1351no need for negative numbers since these are handled by unary
1352operators)."
d240a249
GM
1353 (interactive)
1354 (if (and (not calculator-display-fragile)
1355 calculator-curnum
1356 (string-match "[eE]$" calculator-curnum))
1357 (calculator-digit)
1358 (calculator-op)))
1359
cbb2dddb 1360;;;---------------------------------------------------------------------
25c269ef
DL
1361;;; Input/output modes (not display)
1362
d240a249
GM
1363(defun calculator-dec/deg-mode ()
1364 "Set decimal mode for display & input, if decimal, toggle deg mode."
1365 (interactive)
1366 (if calculator-curnum
1367 (setq calculator-stack
1368 (cons (calculator-curnum-value) calculator-stack)))
1369 (setq calculator-curnum nil)
1370 (if (or calculator-input-radix calculator-output-radix)
1371 (progn (setq calculator-input-radix nil)
1372 (setq calculator-output-radix nil))
1373 ;; already decimal - toggle degrees mode
1374 (setq calculator-deg (not calculator-deg)))
1375 (calculator-update-display t))
1376
86f1e1ec
GM
1377(defun calculator-radix-mode (&optional keys)
1378 "Set input and display radix modes.
1379Optional string argument KEYS will force using it as the keys entered."
d240a249 1380 (interactive)
86f1e1ec
GM
1381 (calculator-radix-input-mode keys)
1382 (calculator-radix-output-mode keys))
d240a249 1383
86f1e1ec
GM
1384(defun calculator-radix-input-mode (&optional keys)
1385 "Set input radix modes.
1386Optional string argument KEYS will force using it as the keys entered."
d240a249
GM
1387 (interactive)
1388 (if calculator-curnum
1389 (setq calculator-stack
1390 (cons (calculator-curnum-value) calculator-stack)))
1391 (setq calculator-curnum nil)
1392 (setq calculator-input-radix
86f1e1ec 1393 (let ((inp (calculator-last-input keys)))
d240a249
GM
1394 (cdr (assq (upcase (aref inp (1- (length inp))))
1395 calculator-char-radix))))
1396 (calculator-update-display))
1397
86f1e1ec
GM
1398(defun calculator-radix-output-mode (&optional keys)
1399 "Set display radix modes.
1400Optional string argument KEYS will force using it as the keys entered."
d240a249
GM
1401 (interactive)
1402 (if calculator-curnum
1403 (setq calculator-stack
1404 (cons (calculator-curnum-value) calculator-stack)))
1405 (setq calculator-curnum nil)
1406 (setq calculator-output-radix
86f1e1ec 1407 (let ((inp (calculator-last-input keys)))
d240a249
GM
1408 (cdr (assq (upcase (aref inp (1- (length inp))))
1409 calculator-char-radix))))
1410 (calculator-update-display t))
1411
cbb2dddb 1412;;;---------------------------------------------------------------------
25c269ef
DL
1413;;; Saved values list
1414
d240a249
GM
1415(defun calculator-save-on-list ()
1416 "Evaluate current expression, put result on the saved values list."
1417 (interactive)
1418 (let ((calculator-add-saved t)) ; marks the result to be added
1419 (calculator-enter)))
1420
1421(defun calculator-clear-saved ()
1422 "Clear the list of saved values in `calculator-saved-list'."
1423 (interactive)
1424 (setq calculator-saved-list nil)
25c269ef 1425 (setq calculator-saved-ptr 0)
d240a249
GM
1426 (calculator-update-display t))
1427
1428(defun calculator-saved-move (n)
1429 "Go N elements up the list of saved values."
1430 (interactive)
1431 (and calculator-saved-list
1432 (or (null calculator-stack) calculator-display-fragile)
1433 (progn
1434 (setq calculator-saved-ptr
1435 (max (min (+ n calculator-saved-ptr)
1436 (length calculator-saved-list))
1437 0))
1438 (if (nth calculator-saved-ptr calculator-saved-list)
1439 (setq calculator-stack
1440 (list (nth calculator-saved-ptr calculator-saved-list))
1441 calculator-display-fragile t)
86f1e1ec
GM
1442 (calculator-reset))
1443 (calculator-update-display))))
d240a249
GM
1444
1445(defun calculator-saved-up ()
1446 "Go up the list of saved values."
1447 (interactive)
1448 (calculator-saved-move +1))
1449
1450(defun calculator-saved-down ()
1451 "Go down the list of saved values."
1452 (interactive)
1453 (calculator-saved-move -1))
1454
cbb2dddb 1455;;;---------------------------------------------------------------------
25c269ef
DL
1456;;; Misc functions
1457
d240a249
GM
1458(defun calculator-open-paren ()
1459 "Equivalents of `(' use this."
1460 (interactive)
86f1e1ec 1461 (calculator-op "("))
d240a249
GM
1462
1463(defun calculator-close-paren ()
1464 "Equivalents of `)' use this."
1465 (interactive)
86f1e1ec 1466 (calculator-op ")"))
d240a249
GM
1467
1468(defun calculator-enter ()
86f1e1ec 1469 "Evaluate current expression."
d240a249 1470 (interactive)
86f1e1ec 1471 (calculator-op "="))
d240a249
GM
1472
1473(defun calculator-backspace ()
1474 "Backward delete a single digit or a stack element."
1475 (interactive)
1476 (if calculator-curnum
1477 (setq calculator-curnum
1478 (if (> (length calculator-curnum) 1)
1479 (substring calculator-curnum
1480 0 (1- (length calculator-curnum)))
1481 nil))
1482 (setq calculator-stack (cdr calculator-stack)))
1483 (calculator-update-display))
1484
1485(defun calculator-clear ()
1486 "Clear current number."
1487 (interactive)
1488 (setq calculator-curnum nil)
1489 (cond
1490 ;; if the current number is from the saved-list - remove it
1491 ((and calculator-display-fragile
1492 calculator-saved-list
1493 (= (car calculator-stack)
1494 (nth calculator-saved-ptr calculator-saved-list)))
1495 (if (= 0 calculator-saved-ptr)
1496 (setq calculator-saved-list (cdr calculator-saved-list))
1497 (let ((p (nthcdr (1- calculator-saved-ptr)
1498 calculator-saved-list)))
1499 (setcdr p (cdr (cdr p)))
1500 (setq calculator-saved-ptr (1- calculator-saved-ptr))))
1501 (if calculator-saved-list
1502 (setq calculator-stack
1503 (list (nth calculator-saved-ptr calculator-saved-list)))
1504 (calculator-reset)))
1505 ;; reset if fragile or double clear
1506 ((or calculator-display-fragile (eq last-command this-command))
1507 (calculator-reset)))
1508 (calculator-update-display))
1509
1510(defun calculator-copy ()
1511 "Copy current number to the `kill-ring'."
1512 (interactive)
1513 (calculator-enter)
25c269ef 1514 ;; remove trailing spaces and and an index
d240a249 1515 (let ((s (cdr calculator-stack-display)))
25c269ef 1516 (if (string-match "^\\([^ ]+\\) *\\(\\[[0-9/]+\\]\\)? *$" s)
d240a249
GM
1517 (setq s (match-string 1 s)))
1518 (kill-new s)))
1519
1520(defun calculator-set-register (reg)
1521 "Set a register value for REG."
1522 (interactive "cRegister to store into: ")
1523 (let* ((as (assq reg calculator-registers))
1524 (val (progn (calculator-enter) (car calculator-stack))))
1525 (if as
1526 (setcdr as val)
1527 (setq calculator-registers
1528 (cons (cons reg val) calculator-registers)))
1529 (message (format "[%c] := %S" reg val))))
1530
1531(defun calculator-put-value (val)
1532 "Paste VAL as if entered.
1533Used by `calculator-paste' and `get-register'."
1534 (if (and (numberp val)
1535 ;; (not calculator-curnum)
1536 (or calculator-display-fragile
1537 (not (numberp (car calculator-stack)))))
1538 (progn
1539 (calculator-clear-fragile)
1540 (setq calculator-curnum (calculator-num-to-string val))
1541 (calculator-update-display))))
1542
1543(defun calculator-paste ()
1544 "Paste a value from the `kill-ring'."
1545 (interactive)
1546 (calculator-put-value
25c269ef 1547 (let ((str (current-kill 0)))
cbb2dddb
GM
1548 (and calculator-paste-decimals
1549 (string-match "\\([0-9]+\\)\\(\\.[0-9]+\\)?\\(e[0-9]+\\)?"
1550 str)
1551 (or (match-string 1 str)
1552 (match-string 2 str)
1553 (match-string 3 str))
1554 (setq str (concat (match-string 1 str)
1555 (or (match-string 2 str) ".0")
1556 (match-string 3 str))))
25c269ef
DL
1557 (condition-case nil (car (read-from-string str))
1558 (error nil)))))
d240a249
GM
1559
1560(defun calculator-get-register (reg)
1561 "Get a value from a register REG."
1562 (interactive "cRegister to get value from: ")
1563 (calculator-put-value (cdr (assq reg calculator-registers))))
1564
1565(defun calculator-help ()
1566 ;; this is used as the quick reference screen you get with `h'
1567 "Quick reference:
1568* numbers/operators/parens/./e - enter expressions
1569 + - * / \\(div) %(rem) _(-X,postfix) ;(1/X,postfix) ^(exp) L(og)
1570 Q(sqrt) !(fact) S(in) C(os) T(an) |(or) #(xor) &(and) ~(not)
1571* >/< repeats last binary operation with its 2nd (1st) arg as postfix op
25c269ef
DL
1572* I inverses next trig function * '/\"/{} - display/display args
1573* D - switch to all-decimal, or toggle deg/rad mode
d240a249
GM
1574* B/O/H/X - binary/octal/hex mode for i/o (X is a shortcut for H)
1575* i/o - prefix for d/b/o/x - set only input/output modes
1576* enter/= - evaluate current expr. * s/g - set/get a register
1577* space - evaluate & save on list * l/v - list total/average
1578* up/down/C-p/C-n - browse saved * C-delete - clear all saved
86f1e1ec 1579* C-insert - copy whole expr. * C-return - evaluate, copy, exit
d240a249
GM
1580* insert - paste a number * backspace- delete backwards
1581* delete - clear argument or list value or whole expression (twice)
1582* escape/q - exit."
1583 (interactive)
1584 (if (eq last-command 'calculator-help)
1585 (let ((mode-name "Calculator")
1586 (major-mode 'calculator-mode)
1587 (g-map (current-global-map))
1588 (win (selected-window)))
1589 (require 'ehelp)
1590 (if calculator-electric-mode
1591 (use-global-map calculator-saved-global-map))
cbb2dddb
GM
1592 (if (or (not calculator-electric-mode)
1593 ;; XEmacs has a problem with electric-describe-mode
1594 (string-match "XEmacs" (emacs-version)))
1595 (describe-mode)
1596 (electric-describe-mode))
d240a249
GM
1597 (if calculator-electric-mode
1598 (use-global-map g-map))
1599 (select-window win) ; these are for XEmacs (also below)
1600 (message nil))
1601 (let ((one (one-window-p t))
1602 (win (selected-window))
1603 (help-buf (get-buffer-create "*Help*")))
1604 (save-window-excursion
1605 (with-output-to-temp-buffer "*Help*"
1606 (princ (documentation 'calculator-help)))
1607 (if one
1608 (shrink-window-if-larger-than-buffer
1609 (get-buffer-window help-buf)))
1610 (message
1611 "`%s' again for more help, any other key continues normally."
1612 (calculator-last-input))
1613 (select-window win)
1614 (sit-for 360))
1615 (select-window win))))
1616
1617(defun calculator-quit ()
1618 "Quit calculator."
1619 (interactive)
1620 (set-buffer calculator-buffer)
1621 (let ((inhibit-read-only t)) (erase-buffer))
1622 (if (not calculator-electric-mode)
1623 (progn
1624 (condition-case nil
1625 (while (get-buffer-window calculator-buffer)
1626 (delete-window (get-buffer-window calculator-buffer)))
1627 (error nil))
1628 (kill-buffer calculator-buffer)))
1629 (setq calculator-buffer nil)
1630 (message "Calculator done.")
1631 (if calculator-electric-mode (throw 'calculator-done nil)))
1632
1633(defun calculator-save-and-quit ()
1634 "Quit the calculator, saving the result on the `kill-ring'."
1635 (interactive)
1636 (calculator-enter)
1637 (calculator-copy)
1638 (calculator-quit))
1639
d240a249
GM
1640(defun calculator-repR (x)
1641 "Repeats the last binary operation with its second argument and X.
1642To use this, apply a binary operator (evaluate it), then call this."
1643 (if calculator-last-opXY
1644 ;; avoid rebinding calculator-last-opXY
1645 (let ((calculator-last-opXY calculator-last-opXY))
1646 (calculator-funcall
1647 (car calculator-last-opXY) x (nth 2 calculator-last-opXY)))
1648 x))
1649
1650(defun calculator-repL (x)
1651 "Repeats the last binary operation with its first argument and X.
1652To use this, apply a binary operator (evaluate it), then call this."
1653 (if calculator-last-opXY
1654 ;; avoid rebinding calculator-last-opXY
1655 (let ((calculator-last-opXY calculator-last-opXY))
1656 (calculator-funcall
1657 (car calculator-last-opXY) (nth 1 calculator-last-opXY) x))
1658 x))
1659
1660(defun calculator-fact (x)
1661 "Simple factorial of X."
1662 (let ((r (if (<= x 10) 1 1.0)))
1663 (while (> x 0)
1664 (setq r (* r (truncate x)))
1665 (setq x (1- x)))
1666 r))
1667
1668(defun calculator-truncate (n)
1669 "Truncate N, return 0 in case of overflow."
1670 (condition-case nil (truncate n) (error 0)))
1671
1672
1673(provide 'calculator)
1674
1675;;; calculator.el ends here