;;; calculate-line.el --- evaluate the current line as a math expression ;; Author: Sean Burke ;; Keywords: tools, convenience ;; Version: 1.2.1 ;; Time-stamp: <2019-08-16 01:47:54 sburke> ;;;====================================================================== ;; ;; See docstring for `calculate-line'. ;; For latest version, check ;; http://interglacial.com/~sburke/pub/emacs/calculate-line.el ;;; History: ;; ;; 2007-12-17 sburke@cpan.org, 1.0.1: ;; - first release ;; ;; 2008-10-22 sburke@cpan.org, 1.1.0: ;; - adding the alias calc-line -> calculate-line. ;; - then just rewording the comments and docstrings ;; ;; ;; 2019-08-16 sburke@cpan.org, 1.2.1: ;; - fixed a fundamental bug based on a bug report ;; - from extremely helpful user Leo Chen ;; ; (defvar calculate-string-scale 10 "The degree of precision in `calculate-string' `bc' calculations") (defun calculate-line () "Runs the contents of the current line thru `calculate-string'. Example: If the current line is: 5325/342 * 18^2 then calling calculate-line adds a line after it: 5044.736842074" ;";"; ; Thanks to Jef Raskin for the idea for this function (interactive) (save-excursion (let ((calc-result (calculate-string ; the current line: (buffer-substring (line-beginning-position) (line-end-position))) )) (end-of-line) (newline) (insert (or calc-result "(error calling 'bc')")) ))) (defalias 'calc-line 'calculate-line) ;====================================================================== (defun calculate-string (string-to-calc) "Run STRING-TO-CALC through `bc'. Returns the result string from `bc', minus any trailing newlines. Note that the result is a string, not a number. If bc runs but reports an error, that should be reflected in the return-value string, but not in any easily identifiable way. Presumably you could try testing whether the return value actually looks like a number." (interactive "sString to calculate value of: ") ; The actual elisp implementation is nightmarish because we have ; to do everything into a temp buffer, in order to accomodate the ; poor match between emacs's IO functions and what 'bc' wants. ; Then we pull bc's output back into a string, and kill the ; temp buffer. (save-match-data ; Safety first! (let ((temp-buffer (get-buffer-create (generate-new-buffer-name " *temp*"))) (calc-result nil) ) (unwind-protect (with-current-buffer temp-buffer (calculate-string-make-bc-program string-to-calc) (calculate-string-run-bc) (calculate-string-spiffing-up) ; ^^ (at the moment, we don't use these retvals) (setq calc-result (buffer-string)) ) ; Finally, the cleanup forms for unwind-protect: (and (buffer-name temp-buffer) (kill-buffer temp-buffer)) (when (interactive-p) (message "Result: %s" calc-result)) ) calc-result ; our retval ))) ;====================================================================== (defun calculate-string-run-bc () "A utility function for `calculate-string', used for feeding the whole current buffer to `bc'." ; Tip: maybe set BC_ENV_ARGS to "-q -l ~/.bcrc" or the like (call-process-region ; Here we go! (point-min) (point-max) ; sending bc the whole buffer "bc" t t nil ; various c-p-r options "-l" ; telling bc to load handy standard libraries ) ) ;====================================================================== (defun calculate-string-make-bc-program (line-to-calculate) "A utility function for `calculate-string' function; this is used for writing the little `bc' program around LINE-TO-CALCULATE." (insert ; making our little bc program here "scale=" (number-to-string calculate-string-scale) ;^^ otherwise 1/3 = 0, because scale's ; default value is somehow zero! "\n" line-to-calculate "\n" ) t ) ;====================================================================== (defun calculate-string-spiffing-up () "A utility function for the `calculate-string', used for cleaning up the output of `bc'." ; Kill final newlines: (goto-char (point-max)) (while (eql 10 (char-before)) (backward-delete-char 1)) ; More spiffing (goto-char (point-min)) (if ; If there was a parse error, throw out the noise, ; if it's in the format we know, namely: ; "(standard_in) 2: parse error" (re-search-forward "^(standard_in) [0-9]+: \\(parse error\\)$" nil t) ; then: (progn (replace-match "<\\1>") nil ; <===== our retval if there was an apparent parse error ) ; Otherwise it's a proper number, so spiff that up... ; (In a sane world, this would be in a string and we'd ; manipulate THAT. However, this is already in a buffer...) ; Kill terminal .00000 etc: (goto-char (point-min)) ; Now a messy thing to replace terminal 0's ; only after a decimal point. (when (search-forward "." nil t) (while (re-search-forward "0+$" nil t) (replace-match "" nil t)) ; a mere "replace-regexp" is noisy ; Now just in case that left us ending with a ".", nix it: (backward-char) ; a (goto-char (point-min)) is unnecc. (when (re-search-forward "\\.$" nil t) (replace-match "" nil t)) ) t ; <====== our retval if there was no apparent parse error ) ) (provide 'calculate-line) ;====================================================================== ;;; calculate-line.el ends here