首页

Sort Sexps

Posted on 2017.08.19, by Chunyang Xu

最近碰到一个需要排序代码的情况,在给 Org Babel 添加了很多语言之后,顺序完全没规律,看起来不方便,给语言按照字母表排个序就好了:

(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . t)
   (sh         . t)
   (lisp       . t)
   (org        . t)
   (perl       . t)
   (R          . t)
   (ruby       . t)
   (python     . t)
   (scheme     . t)
   (C          . t)
   (ditaa      . t)
   (latex      . t)
   (awk        . t)
   (lua        . t)))

这个格式已经很整齐了,稍微再整理下用 M-x sort-linesC-u M-| sort 就能达到目的,但是如果排序的单元不以行为单位,比如:

((emacs-lisp . t) (sh . t) (lisp . t) (org . t) (perl . t) (ruby . t))

这种方法无能为力了。受到网上一些讨论的启发,用 sort-subr 结合 Emacs Syntax Table 能解决问题:

(defun sort-sexps (reverse beg end)
  "Sort sexps in the Region."
  (interactive "*P\nr")
  (save-restriction
    (narrow-to-region beg end)
    (goto-char (point-min))
    (let ((nextrecfun (lambda () (skip-syntax-forward "-.>")))
          (endrecfun  #'forward-sexp))
      (sort-subr reverse nextrecfun endrecfun))))

Emacs 把 Lisp 的 Sexp 的概念扩展到了其他的语言

Basically, a sexp is either a balanced parenthetical grouping, a string, or a symbol

比方说同一个段文字 3 + 2 + 1,在下面的 C 代码中,其中 3, 2 1 分别都是 Sexp,而剩余的空格和操作符号 + 不是,从而导致了 M-x sort-sexps 只排序 3, 2, 1

/* -*- mode: c; -*- */
int x = 3 + 2 + 1;
/* M-x sort-sexps */
int x = 1 + 2 + 3;

而在 Emacs Lisp 代码中,3, +, 2, +, 1 都是 Sexp,所以它们都会被排序。

;; -*- mode: emacs-lisp; -*-
(setq x '(3 + 2 + 1))
;; M-x sort-sexps
(setq x '(+ + 1 2 3))

排序一个 C 字符串数组也能得到预期的结果:

/* -*- mode: c; -*- */
char *characters[] = { "Tom", "Jerry", "Nibbles", "Quacker" };
/* M-x sort-sexps */
char *characters[] = { "Jerry", "Nibbles", "Quacker", "Tom" };

sort-sexps 用的是 sort-subr 默认的比较方法(即用 string< 比较整个 Record),通过设置 sort-subr 的参数可以做调整。比如按照数字的大小

(defun sort-numbers (reverse beg end)
  "Sort numbers in the Region."
  (interactive "*P\nr")
  (save-restriction
    (narrow-to-region beg end)
    (goto-char (point-min))
    (let ((nextrecfun (lambda () (skip-syntax-forward "-.>")))
          (endrecfun  #'forward-sexp)
          (startkeyfun (lambda ()
                         (or (number-at-point)
                             (user-error "Sexp doesn't looks like a number")))))
      (sort-subr reverse nextrecfun endrecfun startkeyfun))))


'(3 1 4 10 2)

;; sort-sexps
'(1 10 2 3 4)

;; sort-numbers
'(1 2 3 4 10)