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.")))))))
Sergey
  • 249
  • 1
  • 8
  • What have you tried? –  Mar 05 '19 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 05 '19 at 12:25

1 Answers1

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}
Tobias
  • 32,569
  • 1
  • 34
  • 75
  • 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 05 '19 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 05 '19 at 16:11
  • I understand. But one last thing. Why is function not jumping to unmatched character position anymore? – Sergey Mar 05 '19 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 05 '19 at 16:32