понедельник, 1 декабря 2008 г.

Быстрый ёфикатор для SXEmacs

Сегодня, в день рождения Карамзина, я покажу как в SXEmacs можно сделать быстрый полу-автоматический ёфикатор текста. Для начала демонстрация: Для выполнения такой задачи уже существовал скрипт и база слов Евгения Миньковского для XEmacs, но у этого решения есть несколько недостатков:
  • Время загрузки базы слов в hash-таблицу (serialization) крайне огромное и этому есть объяснения.
  • В скрипте содержатся не-ascii символы, это скорее эстетический недостаток, а не реальный.
Я поэкспериментировал немного, посоветовался с разработчиками XEmacs и SXEmacs и пришёл к выводу, что сделать более быструю (чем O(n)) сереализацию базы слов в hash-таблицу пока не представляется возможным. С другой стороны, я был наслышан, что третья реализация FTS(Full Text Search) в SQLite3 достаточно быстра, к тому же, у SQLite3 хороший и простой API. Поэтому было решено написать ffi-sqlite.el -- FFI(Foreign Function Interface) к библиотеке sqlite3, что и было сделано в течение пары часов. (На данный момент ffi-sqlite.el уже находится в main репозитарии SXEmacs) После того как в моих руках оказалась связка SXEmacs+SQLite из базы слов Евгения Миньковского была создана sqlite база с двумя FTS таблицами:
  1. Для слов, в которых написание ё точно определено
  2. Для слов, в которых возможно написание ё
Далее дело техники. Немного адаптируем код Евгения и получаем:
(defcustom yo-db-file (expand-file-name "yo.db" user-init-directory)
"File with yo database.")

(defcustom yo-cutting-strings (list "\\-" "\"=" "\"~")
"Words in the text may be splitting by some strings:
for example: hy\\-phe\\-na\\-ti\\-on in TeX")

(defconst yo-cutting-regexp
  (concat "\\(?:"
        (mapconcat 'regexp-quote
                   yo-cutting-strings "\\|")
        "\\)"))

;; Under Mule: 1493 - little e, 1461 - big E
(defconst yo-e-word-regexp
  (concat "\\(?:\\w\\(?:\\w\\|" yo-cutting-regexp "\\)*\\)?"
        "\\(?:" (char-to-string (int-to-char 1493)) "\\|"
        (char-to-string (int-to-char 1461)) "\\)"
        "\\(?:\\w\\(?:\\w\\|" yo-cutting-regexp "\\)*\\)?"))

(defvar yo-db nil)

(defun yo-word (word &optional table)
"Return yo variant of the WORD.
Return value:
nil     - no yo variant
string  - unambiguous yo variant
(maybe . string) - ambiguous yo variant"
  (unless yo-db
  (setq yo-db (sqlite-open yo-db-file)))
  (let* ((sql (format "select val from %S where key match ?"
                    (or table 'onlyyo) word))
       (eword (encode-coding-string word 'utf-8))
       (result (car (car (sqlite-rows yo-db sql (list eword))))))
  (if result
      (decode-coding-string result 'utf-8)
    (when (or (null table) (eq table 'onlyyo))
      (let ((mres (yo-word word 'maybeyo)))
        (when mres (cons 'maybe mres)))))))

(defun yofy-region (begin end)
"Yofy the region."
  (interactive "r")
  (save-restriction
  (save-excursion
    (narrow-to-region begin end)
    (beginning-of-buffer)
    (let (current-e-word current-yo-word)
      (while (re-search-forward yo-e-word-regexp nil t)
        (save-match-data
          (setq current-e-word
                (downcase
                 (replace-in-string
                  (match-string 0) yo-cutting-regexp "")))
          (setq current-yo-word (yo-word current-e-word)))

        (cond ((stringp current-yo-word)
               (replace-match current-yo-word nil))
              ((and (consp current-yo-word)
                    (eq (car current-yo-word) 'maybe))
               (setq current-yo-word (cdr current-yo-word))
               (if search-highlight
                   (isearch-highlight (match-beginning 0) (match-end 0)))
               (when (y-or-n-p (format "\"%s\" --> \"%s\"? "
                                       current-e-word current-yo-word))
                 (undo-boundary)
                 (replace-match current-yo-word nil))
               (isearch-dehighlight))))
      ))))

(defun yofy-buffer ()
"Yofy whole buffer."
  (interactive)
  (yofy-region (point-min) (point-max)))

(defun yofy ()
"Yofy region or buffer."
  (interactive)
  (if (region-active-p)
    (yofy-region (region-beginning) (region-end))
  (yofy-buffer)))
Кладём базу слов (5.3M) в ~/.sxemacs/, вставляем код в init.el и всё. Теперь у нас появилась мощная команда - M-x yofy RET, которую вы можете забиндить на какую-нибудь кнопку. В моём случае она висит на C-m e y.

Комментариев нет: