Elisp: Validate Matching Brackets ๐Ÿ“œ

By Xah Lee. Date: . Last updated: .

Problem

Write a emacs lisp script to process ten thousand files and check for mismatched brackets.

The matching pairs includes these: () {} [] โ€œโ€ โ€นโ€บ ยซยป ใ€ˆใ€‰ ใ€Šใ€‹ ใ€ใ€‘ ใ€–ใ€— ใ€Œใ€ ใ€Žใ€. [see Unicode: Brackets ใ€Œใ€ใ€ใ€‘ใ€Šใ€‹]

The program should be able to check all files in a dir, and report any file that has mismatched bracket, and also indicate the line number or position where a mismatch occurs.

Solution

;; -*- coding: utf-8; lexical-binding: t; -*-

;; 2026-05-25
;; Elisp: Validate Matching Brackets ๐Ÿ“œ
;; http://xahlee.info/emacs/emacs/elisp_validate_matching_brackets.html

(defvar xah-validate-brackets-alist nil "a alist of brackets.
Each element is a cons pair, of the left and right bracket.
Each is a string of single char.")

(setq xah-validate-brackets-alist
      '(
        ("โ€œ" . "โ€")
        ;; ("โ€น" . "โ€บ")
        ;; ("ยซ" . "ยป")
        ("ใ€" . "ใ€‘")
        ("ใ€”" . "ใ€•")
        ("ใ€–" . "ใ€—")
        ("ใ€ˆ" . "ใ€‰")
        ("ใ€Š" . "ใ€‹")
        ("ใ€Œ" . "ใ€")
        ("ใ€Ž" . "ใ€")
        ("โฎ" . "โฏ")
        ("โฐ" . "โฑ")
        ;; ("{" . "}")
        ;; ("[" . "]")
        ;; ("(" . ")")
        ))

(defun xah-validate-brackets-file (Filepath)
  "Validate brackets of file at Filepath.
The type of brackets to check is stored in variable `xah-validate-brackets-alist'.

When called interactively, it checks current buffer. Also, it saves first if modified.

URL `http://xahlee.info/emacs/emacs/elisp_validate_matching_brackets.html'
Created: 2024-08-10
Version: 2026-05-25"
  (interactive
   (progn
     (when (buffer-modified-p)
       (save-buffer))
     (list buffer-file-name)))
  (let ((xoutbuf (get-buffer-create "*validate brackets out*" t))
        xregex  xstack xchar xpos)
    (when (not (file-exists-p Filepath)) (user-error "Error: file no exist: %s" Filepath))
    (display-buffer xoutbuf)
    (with-current-buffer xoutbuf
      (goto-char (point-min))
      (when (not (string-match "mode: xah-find-output" (buffer-substring-no-properties (pos-bol) (pos-eol)))) (insert "-*- mode: xah-find-output -*-\n"))
      (when (fboundp 'xah-find-output-mode) (xah-find-output-mode)))
    (setq xregex
          (mapconcat
           (lambda (x) (concat (regexp-quote (car x)) "\\|" (regexp-quote (cdr x))))
           xah-validate-brackets-alist "\\|"))

    ;; (princ "\n" xoutbuf)

    (with-temp-buffer
      (insert-file-contents Filepath)
      (goto-char 1)
      (while (re-search-forward xregex nil t)
        (setq xpos (point))
        (setq xchar (char-to-string (char-before)))
        (let (xrightBra-p xleftBra)
          (setq xrightBra-p (rassoc xchar xah-validate-brackets-alist))
          (when xrightBra-p (setq xleftBra (car xrightBra-p)))
          (if xstack
              (if (string-equal (elt (car xstack) 0) xleftBra)
                  (pop xstack)
                (push (vector xchar xpos) xstack))
            (if xrightBra-p
                (princ
                 (format "Nothing matches this bracket: โŸจ%sโŸฉ
โ€ขat position: %s
โ€ขat file: %s
"
                         xchar xpos Filepath)
                 xoutbuf)

              (push (vector xchar xpos) xstack)))))
      (if xstack
          (princ
           (format
            "Nothing matches this bracket: โŸจ%sโŸฉ
โ€ขat position: %s
โ€ขat file: %s
"
            (aref (car xstack) 0)
            (aref (car xstack) 1)
            Filepath)
           xoutbuf)))))

(defun xah-validate-brackets-dir (Dir &optional FileExtension)
  "Validate brackets in all files in Dir, recursively.
The type of brackets to check is stored in var `xah-validate-brackets-alist'.

Check only files with FileExtension. If FileExtension is nil, do all files.

Any file or dir name starting with dot is ignored.

URL `http://xahlee.info/emacs/emacs/elisp_validate_matching_brackets.html'
Created: 2024-08-11
Version: 2026-05-25"
  (interactive
   (list default-directory
         (if buffer-file-name
             (if (file-name-extension buffer-file-name)
                 (file-name-extension buffer-file-name)
               "html"
               )
           "html")))
  (let (xpaths xextRegex)
    (setq xextRegex
          (if FileExtension
              (concat "\\." FileExtension "\\'")
            ""))
    (setq xpaths
          (directory-files-recursively
           Dir
           xextRegex
           nil
           (lambda (x) (not (string-match "/\\..*/" x)))))
    (mapc 'xah-validate-brackets-file xpaths)))

Video Tutorial

xah talk show 2024-08-10 hn4V9
Xah Talk Show 2024-08-10 Ep572, Emacs Lisp, Validate Matching Brackets