xyzzy でメジャーモードを簡単に作れるように

11/04/20 キーワードを読み込むときに大文字小文字を区別するように変更

xyzzy でメジャーモードを作るとき、ちゃんとしたものを作るとなれば時間も手間もかかるのですが、簡単なものならだいたいやるべきことは決まっています。

  • フックを作る
  • キーワードファイルを読み込む
  • シンタックステーブルを設定
  • キーマップを設定
  • 拡張子に関連付ける

そこでなんちゃってメジャーモードを簡単に作れるように共通処理を関数化してみました。

(defun majar-mode-template (name &key keyword syntax key ext)
  """
  メジャーモードのテンプレートです。
  name     メジャーモード名のシンボルです。xxx-mode の xxx にあたります。
           またモード表示行の括弧内には Xxx(nstring-capitalize name) を
           表示します。
  keyword  t ならばファイル名 Xxx をキーワードファイルとして読み込みます。
  syntax   シンタックステーブルを渡すとそれを利用します。
  key      キーマップを渡すとそれを利用します。
  ext      正規表現を渡すとマッチするファイル名とモードを関連付けます。
  """
  (flet ( 
    ; フォーマットに従ってシンボルを生成する関数
    (symbol (f) (intern (format nil f (symbol-name name)))))
    (let* (
      (mode (symbol "~A-mode"))
      (mode-name (nstring-capitalize (format nil "~A" (symbol-name name))))
      (mode-hook (symbol "*~A-mode-hook*"))
      (keyword-table (symbol "*~A-keyword-hash-table*"))
      (keyword-file mode-name)
      (syntax-table (symbol "*~A-mode-syntax-table*"))
      (key-map (symbol "*~A-key-map*")))
      (eval `(progn 
        ; スペシャル変数の定義
        (defvar ,mode-hook nil)
        
        ,(when key `(defvar ,key-map ',key))
        ,(when keyword `(defvar ,keyword-table nil))
        ,(when syntax `(defvar ,syntax-table ,syntax))
        ; モード関数の作成
        (defun ,mode ()
          (interactive)
          
          ; ローカル変数の削除
          (kill-all-local-variables)
        
          ; 名前二つほど設定
          (setq buffer-mode ',mode)
          (setq mode-name ,mode-name)
          
          ; キーワードの読み込み処理
          ,(if keyword `(progn
            (make-local-variable 'keyword-hash-table)
            (setq keyword-hash-table
              (setq ,keyword-table (or ,keyword-table
                (load-keyword-file ,keyword-file nil))))))
          
          ; シンタックステーブルの適用
          ,(if syntax `(use-syntax-table ,syntax-table))
          
          ; キーマップの適用
          ,(if key `(use-keymap ,key-map))
          
          ; フックを走らせる
          (run-hooks ',mode-hook))))
      
      ; モードを拡張子と対応付ける
      (when ext
        (push (cons ext mode) *auto-mode-alist*)))))

この関数を使うと、たとえば謹製なんちゃって scala-mode は以下のように書けます

(provide "scala-mode")
(majar-mode-template 'scala
  :keyword t
  :syntax (let ( (st (make-syntax-table)) )
    (set-syntax-start-c++-comment   st #\/)
    (set-syntax-end-c++-comment     st #\LFD)
    (set-syntax-start-multi-comment st "/*")
    (set-syntax-end-multi-comment   st "*/")
    (set-syntax-string              st #\")
    (set-syntax-match               st #\( #\))
    (set-syntax-match               st #\{ #\})
    (set-syntax-match               st #\[ #\])
    st)
  :key (let ( (m (make-sparse-keymap)) )
    (define-key m #\RET 'newline-and-indent)
    ... 好きなようにキー登録
    m)
  :ext "\\.scala$")