2

Inspired by this blog I wrote a function which replaces arithmetic expressions between two $ symbols with the mathematical result inline (and deletes the $ symbols). How can I modify this such that it recognizes if the expression is invalid and prints an error message (without without modifying or replacing the expression) in this case in the minibuffer.

(require 'calc)
(defun calc-eval-and-insert ()
  "Replaces an arithmetic expression between two $ sympols with the result. Example expression: $2*(3 + round(5.5))$" 
  (interactive)
  (let (result p1 p2)
    (skip-chars-backward "^$“")
    (setq p1 (point))
    (skip-chars-forward "^$“")
    (setq p2 (point))
    (setq result (calc-eval (buffer-substring-no-properties p1 p2)))
    (goto-char p1)
    (delete-char -1)
    (setq p1 (point))
    (goto-char p2)
    (delete-char -1)    
    (delete-region p1 p2)
    ;(insert " = " result)
    (insert result)
    )
  )


(global-set-key (kbd "C-c r") 'calc-eval-and-insert)

Since I am a beginner in elisp, other suggestions of how to improve the code are also welcome.

Edit

I just noticed that executing it on $2 + 2.2.2$ results in a "Bad Format" error (as expected). But for example $roundd(2.2)$ (instead of $round(2.2)$ results in no error messages but replaces the expression with round(2.2).

  • @Dan I disagree that this question shouldn't be tagged elisp. It's intrinsically about Emacs Lisp as a language. Not about the fundamentals of Lisp, but about Emacs Lisp. – Gilles Aug 19 '16 at 22:35
  • @Gilles: yup, I didn't read it closely enough the first time. – Dan Aug 19 '16 at 22:37
2

As it says in the documentation of calc-eval,

Return value will either be the formatted result in string form, or a list containing a character position and an error message in string form.

So check whether the return value is a string or a list. If it's a list, don't modify the buffer.

Some other improvements:

  • Don't force the loading of calc. calc-eval is autoloaded so you don't need to do anything, the module will be loaded automatically when your function is called.
  • Your code breaks if there isn't a valid opening character before or a valid closing character after the cursor.
  • Your code breaks when the expression is the very last thing in the buffer, because when you delete a character, this effectively moves p2 one position forward.
  • If the cursor is on a $, it would be nice to allow it to be an opening $ rather than systematically looking backward. This can't be perfect (when the cursor is on the middle $ of $1+1$2+2$, looking left and looking right are equally appealing).
  • Your code may look very far before and after the cursor. I think you should stick to the same line.
  • Don't move the cursor if the boundaries of the arithmetic expression can't be found. Whether to move the cursor in case of a calc error is debatable, I think it makes sense to move to the error location reported by calc.
  • Put * in the interactive specification so that the function stops immediately if the buffer is read-only. Put @ so that the function changes windows if you invoke it by clicking.
  • A matter of style: avoid declaring variables and assigning them later.
  • Also style: give variables more meaningful names than p1 and p2.
(defun calc-eval-and-insert ()
  "Replaces an arithmetic expression between two $ sympols with the result. Example expression: $2*(3 + round(5.5))$" 
  (interactive "@*")
  (let* ((orig (point))
         (beg (save-excursion
                (if (and (eolp) (not (bolp)))
                    ;; If we're on the last character of a line, look before.
                    (backward-char))
                (skip-chars-backward "^\n$“")
                (if (bolp)
                    ;; If there was no expression start character before, try one
                    ;; at the original point.
                    (goto-char (1+ orig)))
                (unless (memq (char-after (1- (point))) '(?$ ?“))
                  (error "Start of arithmetic expression not found"))
                (point)))
         (end (save-excursion
                (goto-char (1+ beg))
                (skip-chars-forward "^\n$“")
                (unless (memq (char-after (point)) '(?$ ?”))
                  (error "End of arithmetic expression not found"))
                (point)))
         (result (calc-eval (buffer-substring-no-properties beg end))))
    (if (stringp result)
        (progn
          (delete-region (1- beg) (1+ end))
          (insert result))
      (goto-char (+ beg (car result)))
      (error "Calc error: %s" (cadr result)))))
  • Thanks a lot for your many improvements! Regarding the main question: If you understand you correctly you mean that the error handling will should happen automatically in my code. But it doesn't in all cases, for example if you use an unknown function name. I made this clear in my edit above. – student Aug 19 '16 at 23:05
  • @student Calc can do symbolic computation. As far as it's concerned, roundd(2.2) is a valid expression. If you want to check whether the expression is numeric, that's a different problem which is about calc and not about elisp, please ask a new question. – Gilles Aug 19 '16 at 23:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.