3

I've found a function for checking parenthesis/brackets balance. Full code is in the bottom. I wanted to use it to check LaTeX commands like \left(/\right), \[/\] etc.

How can I modify this function to accept such constructs?

(defun xah-check-parens-balance ()
  "Check if there are unbalanced parentheses/brackets/quotes in current bufffer or selection.
If so, place cursor there, print error to message buffer.

URL `http://ergoemacs.org/emacs/emacs_check_parens_balance.html'
Version 2018-07-03"
  (interactive)
  (let* (
         ($bracket-alist
          '( (?“ . ?”) (?‹ . ?›) (?« . ?») (?【 . ?】) (?〖 . ?〗) (?〈 . ?〉) (?《 . ?》) (?「 . ?」) (?『 . ?』) (?{ . ?}) (?\[ . ?\]) (?\( . ?\))))
         ;; regex string of all pairs to search.
         ($bregex
          (let (($tempList nil))
            (mapc
             (lambda (x)
               (push (char-to-string (car x)) $tempList)
               (push (char-to-string (cdr x)) $tempList))
             $bracket-alist)
            (regexp-opt $tempList )))
         $p1
         $p2
         ;; each entry is a vector [char position]
         ($stack '())
         ($char nil)
         $pos
         $is-closing-char-p
         $matched-open-char
         )
    (if (region-active-p)
        (setq $p1 (region-beginning) $p2 (region-end))
      (setq $p1 (point-min) $p2 (point-max)))

    (save-excursion
      (save-restriction
        (narrow-to-region $p1 $p2)
        (progn
          (goto-char 1)
          (while (re-search-forward $bregex nil "move")
            (setq $pos (point))
            (setq $char (char-before))
            (progn
              (setq $is-closing-char-p (rassoc $char $bracket-alist))
              (if $is-closing-char-p
                  (progn
                    (setq $matched-open-char
                          (if $is-closing-char-p
                              (car $is-closing-char-p)
                            (error "logic error 64823. The char %s has no matching pair."
                                   (char-to-string $char))))
                    (if $stack
                        (if (eq (aref (car $stack) 0) $matched-open-char )
                            (pop $stack)
                          (push (vector $char $pos) $stack ))
                      (progn
                        (goto-char $pos)
                        (error "First mismtach found. the char %s has no matching pair."
                               (char-to-string $char)))))
                (push (vector $char $pos) $stack ))))
          (if $stack
              (progn
                (goto-char (aref (car $stack) 1))
                (message "Mismtach found. The char %s has no matching pair." $stack))
            (print "All brackets/quotes match.")))))))
  • What have you tried? – DoMiNeLa10 Mar 5 at 12:06
  • @DoMiNeLa10 I've tried to add constructs like (?\\\[ . ?\\\]”) to the list, but it resulted in syntax errors. I'm don't really know lisp. – Sergey Mar 5 at 12:25
2

You find an adapted version of the Elisp code below. The customizable option xah-check-bracket-alist holds the list of matching delimiters. I only added \(, (, \left(, \[, [, and \left[ and the corresponding closing delimiters to the list. You can extend the list via customization.

(defcustom xah-check-bracket-alist
  '(
    ("\\(" . "\\)")
    ("(" . ")")
    ("\\left(" . "\\right)")
    ("\\[" . "\\]")
    ("[" . "]")
    ("\\left[" . "\\right]")
    )
  "Pairs of matching opening and closing delimiters."
  :type '(repeat (cons :tag "Pair of opening and closing delimiter" (string :tag "Opening delimiter") (string :tag "Closing delimiter")))
  :group 'matching
  )

(defun xah-check-parens-balance ()
  "Check if there are unbalanced parentheses/brackets/quotes in current bufffer or selection.
If so, place cursor there, print error to message buffer.

URL `http://ergoemacs.org/emacs/emacs_check_parens_balance.html'
Version 2018-07-03"
  (interactive)
  (let* (($bracket-alist xah-check-bracket-alist)
         ;; regex string of all pairs to search.
         ($bregex
      (regexp-opt
       (append (mapcar #'car $bracket-alist)
           (mapcar #'cdr $bracket-alist))))
         $p1
         $p2
         ;; each entry is a vector [char position]
         ($stack '())
         ($char nil)
         $pos
         $is-closing-char-p
         $matched-open-char
         )
    (if (region-active-p)
        (setq $p1 (region-beginning) $p2 (region-end))
      (setq $p1 (point-min) $p2 (point-max)))

    (let ((old-point (point)))
      (save-restriction
        (narrow-to-region $p1 $p2)
        (goto-char 1)
        (while (re-search-forward $bregex nil "move")
          (setq $pos (point))
          (setq $char (match-string 0))
          (progn
            (setq $is-closing-char-p (rassoc $char $bracket-alist))
            (if $is-closing-char-p
                (progn
                  (setq $matched-open-char
                        (or (car $is-closing-char-p)
                            (error "Logic error 64823.  The char %s has no matching pair"
                                   $char)))
                  (if $stack
                      (if (equal (aref (car $stack) 0) $matched-open-char)
                          (pop $stack)
                        (push (vector $char $pos) $stack))
                    (progn
                      (goto-char $pos)
                      (error "First mismtach found.  The char %s has no matching pair"
                             $char))))
              (push (vector $char $pos) $stack)))))
  (goto-char old-point))
    (if $stack
        (let* (($vec (car $stack))
           ($str (aref $vec 0))
           ($pos (aref $vec 1)))
          (goto-char $pos)
          (message "Mismtach found. The char %s at buffer position %d has no matching pair." $str $pos))
      (print "All brackets/quotes match."))))

Tested with Emacs 26.1 called as emacs -Q.

Gives "All brackets/quotes match" for the following text and point does not move.

\documentclass{article}

\begin{document}
\begin{align*}
  \left(\right)
  something = \left(x^2 + y^2\right)\\
  other = \left(a+b\right).q
\end{align*}
\end{document}

Gives "Mismtach found. The char ) at buffer position 133 has no matching pair." for the following text and point moves to the last parenthesis.

\documentclass{article}

\begin{document}
\begin{align*}
  \left(\right]
  something = \left(x^2 + y^2\right)\\
  other = \left(a+b).q
\end{align*}
\end{document}

Gives "Mismtach found. The char \right] at buffer position 73 has no matching pair." for the following text and point moves to the end of \right].

\documentclass{article}

\begin{document}
\begin{align*}
  \left(\right]
  something = \left(x^2 + y^2\right)\\
  other = \left(a+b\right).q
\end{align*}
\end{document}
  • It seems to only check, if overall number of opening characters match number of closing characters. For example, (] and \left(} are considered balanced. Additionally, I cannot make it check quotes. – Sergey Mar 5 at 15:40
  • @Sergey Note that the opening delimiter must differ from the closing delimiter. Quotes only work if the opening quote is not equal the closing quote (which is possible with utf8-chars). Furthermore, I didn't touch the logic of the program. I just made it work with strings instead of chars. – Tobias Mar 5 at 16:11
  • I understand. But one last thing. Why is function not jumping to unmatched character position anymore? – Sergey Mar 5 at 16:30
  • @Sergey It jumps now to the error. Just update. The version from your question is not jumping to the error. – Tobias Mar 5 at 16:32

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.