Elisp: Validate Matching Brackets ๐Ÿš€

By Xah Lee. Date: . Last updated: .

Video Tutorial

xts 2024-08-10 hn4V9
Xah Talk Show 2024-08-10 Ep572, Emacs Lisp, Validate Matching Brackets

Problem

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

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

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; -*-

(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 var `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: 2024-08-12"
  (interactive
   (progn
     (when (buffer-modified-p)
       (save-buffer))
     (list buffer-file-name)))
  (let (xregex  xstack xchar xpos)
    (when (not (file-exists-p Filepath)) (user-error "Error: file no exist: %s" Filepath))
    (setq xregex
          (mapconcat
           (lambda (x) (concat (regexp-quote (car x)) "\\|" (regexp-quote (cdr x))))
           xah-validate-brackets-alist "\\|"))
    (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
                (warn
                 "Bracket mismatch in file %s.
Nothing matches this bracket: %s.
at position %s."
                 Filepath xchar xpos
                 )
              (push (vector xchar xpos) xstack)))))
      (if xstack
          (warn
           "Bracket mismatch in file %s.
Nothing matches this bracket: %s.
at position %s."
           Filepath (aref (car xstack) 0)
           (aref (car xstack) 1))
        (message "Brackets valid. You are beautiful.")))))

(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: 2024-08-14"
  (interactive
   (list default-directory
         (if buffer-file-name
             (if (file-name-extension buffer-file-name)
                 (file-name-extension buffer-file-name)
               nil
               )
           nil)))
  (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)))