[インデックス 16866] ファイルの概要
このコミットは、Go言語のEmacsメジャーモードであるgo-mode.el
における変更を記録しています。具体的には、括弧の対応する開始位置へ移動する機能go-goto-opening-parenthesis
の実装が、Emacs Lispの組み込み関数backward-up-list
を使用するように変更されています。これにより、コードの簡素化と堅牢性の向上が図られています。
コミット
commit 6e37bc1eecb41838d9f7bdc27a5f323b1d92ff0e
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date: Wed Jul 24 13:48:04 2013 -0400
misc/emacs: replace our go-goto-opening-parenthesis with backward-up-list
R=adonovan
CC=golang-dev
https://golang.org/cl/11524045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6e37bc1eecb41838d9f7bdc27a5f323b1d92ff0e
元コミット内容
misc/emacs: replace our go-goto-opening-parenthesis with backward-up-list
R=adonovan
CC=golang-dev
https://golang.org/cl/11524045
変更の背景
この変更の背景には、Emacs Lispにおけるコードの慣用的な記述と効率性の追求があります。go-mode.el
はGo言語のコードをEmacsで編集する際に便利な機能を提供するメジャーモードであり、その中には括弧の対応関係を扱う機能も含まれています。
以前のgo-goto-opening-parenthesis
関数は、Go言語特有の構文解析の複雑さや、Emacsの汎用的な括弧移動機能では対応しきれないケースを考慮して、独自に実装されていた可能性があります。しかし、Emacs Lispには構造化されたテキスト(S式、括弧で囲まれたリストなど)を効率的にナビゲートするための強力な組み込み関数が多数存在します。
このコミットの目的は、独自に実装されていたgo-goto-opening-parenthesis
関数を、Emacs Lispの標準的な組み込み関数であるbackward-up-list
に置き換えることで、以下の利点を得ることです。
- コードの簡素化: 複雑な手動実装を、より簡潔でテスト済みの組み込み関数に置き換えることで、コードベースが小さくなり、理解しやすくなります。
- 堅牢性の向上: 組み込み関数はEmacsのコア開発者によってメンテナンスされており、広範なテストと使用実績があるため、独自実装よりもバグが少なく、様々なエッジケースに対して堅牢である可能性が高いです。
- Emacs Lispの慣用的な利用: Emacs Lispの強力な機能を活用することで、より「Emacsらしい」コードになり、将来的なメンテナンスや他のEmacs Lisp開発者による理解が容易になります。
- パフォーマンスの改善: 組み込み関数はC言語で実装されていることが多く、Emacs Lispで書かれた同等の機能よりも高速に動作する可能性があります。
この変更は、go-mode.el
の品質と保守性を向上させるための、一般的なリファクタリングの一環と見なすことができます。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
Emacs Lisp (Elisp)
Emacs Lispは、Emacsエディタの拡張言語です。Emacsのほぼ全ての機能はEmacs Lispで書かれており、ユーザーはEmacs Lispを使ってEmacsをカスタマイズしたり、新しい機能を追加したりできます。Emacs LispのコードはS式(Symbolic Expression)と呼ばれる括弧で囲まれたリスト構造で記述されます。
Emacsのナビゲーション機能
Emacsはテキスト編集において非常に強力なナビゲーション機能を提供します。特に、括弧やブロック構造を意識した移動は、プログラミングにおいて頻繁に利用されます。
backward-up-list
: Emacs Lispの組み込み関数で、現在のカーソル位置から見て、外側の括弧の開始位置(開き括弧)に移動します。例えば、(foo (bar |baz))
という位置にカーソルがある場合、backward-up-list
を実行すると(foo |(bar baz))
の(
の位置に移動します。この関数は、LispのようなS式だけでなく、C言語の{}
や()
、[]
など、Emacsが構文的に認識する様々な種類の括弧に対応しています。condition-case
: Emacs Lispのエラーハンドリング機構です。Common Lispのhandler-case
に似ています。condition-case
は、指定されたフォーム(コードブロック)の実行中に特定のエラー(条件)が発生した場合に、そのエラーを捕捉し、対応するハンドラを実行します。- 構文:
(condition-case VAR BODY-FORM HANDLER...)
VAR
: エラー情報が格納される変数(通常はnil
で無視される)。BODY-FORM
: 実行されるコードブロック。HANDLER
:(CONDITION-NAME HANDLER-FORM...)
の形式で、捕捉するエラーとそのハンドラを指定します。
- 構文:
scan-error
: Emacs Lispで、構文解析(スキャン)中にエラーが発生したことを示す条件(エラータイプ)です。例えば、対応する括弧が見つからない場合などに発生します。
go-mode.el
go-mode.el
は、EmacsでGo言語のソースコードを編集するためのメジャーモードです。Go言語の構文ハイライト、インデント、コメントアウト、そしてこのコミットで扱われているような括弧の対応関係を考慮したナビゲーション機能などを提供します。
技術的詳細
このコミットの技術的詳細は、go-mode.el
内のgo-goto-opening-parenthesis
関数の実装変更に集約されます。
変更前のgo-goto-opening-parenthesis
変更前のgo-goto-opening-parenthesis
関数は、以下のようなロジックで実装されていました。
(defun go-goto-opening-parenthesis (&optional char)
(let ((start-nesting (go-paren-level)))
(while (and (not (bobp))
(>= (go-paren-level) start-nesting))
(if (zerop (skip-chars-backward
(if char
(case char (?\\] "^[") (?\\} "^{\") (?\\) "^("))
"^[{(\")))\n (if (go-in-string-or-comment-p)\n (go-goto-beginning-of-string-or-comment)\n (backward-char))))))
この実装は、go-paren-level
という関数(おそらく現在の括弧のネストレベルを返す)と、skip-chars-backward
(指定された文字セットを後方にスキップする)を組み合わせて、手動で開き括弧を探していました。また、文字列やコメント内での移動を避けるためのロジック(go-in-string-or-comment-p
)も含まれていました。この手動での実装は、Go言語の特定の構文規則や、Emacsのバッファ操作の複雑さを考慮する必要があり、潜在的にバグを含みやすいものでした。特に、skip-chars-backward
とbackward-char
を組み合わせたループは、効率性や正確性の面で課題を抱える可能性があります。
変更後のgo-goto-opening-parenthesis
変更後のgo-goto-opening-parenthesis
関数は、以下のように大幅に簡素化されました。
(defun go-goto-opening-parenthesis (&optional legacy-unused)
;; The old implementation of go-goto-opening-parenthesis had an
;; optional argument to speed up the function. It didn't change the
;; function's outcome.
;; Silently fail if there's no matching opening parenthesis.
(condition-case nil
(backward-up-list)
(scan-error nil)))
この新しい実装では、以下の点が重要です。
backward-up-list
の利用: 独自の手動実装を完全に削除し、Emacs Lispの組み込み関数backward-up-list
を呼び出すだけになりました。backward-up-list
は、Emacsの構文解析エンジンを利用して、現在のカーソル位置から見て対応する開き括弧に移動する、より高レベルで堅牢な機能を提供します。condition-case
によるエラーハンドリング:backward-up-list
は、対応する開き括弧が見つからない場合にscan-error
というエラーを発生させることがあります。このエラーを捕捉するためにcondition-case
が使用されています。scan-error nil
というハンドラは、scan-error
が発生した場合に何もしない(nil
を返す)ことを意味し、これにより関数がエラーで停止することなく、静かに失敗する(つまり、カーソルが移動しない)ようになります。これは、ユーザー体験を損なわないための重要な配慮です。legacy-unused
引数: 以前の関数が持っていたオプション引数char
は、新しい実装では不要になりましたが、既存のコードからの呼び出しとの互換性を保つためにlegacy-unused
という名前で残されています。これは、関数のシグネチャを変更せずに、内部実装を刷新する一般的な手法です。
変更の影響
この変更により、go-mode.el
のgo-goto-opening-parenthesis
関数は、より簡潔で、Emacsの標準的な機能に依存するようになりました。これにより、コードの可読性、保守性、そして堅牢性が向上します。特に、backward-up-list
はEmacsのコア機能であるため、Go言語の構文解析の複雑さを意識することなく、正確な括弧移動が期待できます。
コアとなるコードの変更箇所
変更はmisc/emacs/go-mode.el
ファイルに集中しています。
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -87,6 +87,18 @@
(concat \"\\\\<\" s \"\\\\>\")
(concat \"\\\\_<\" s \"\\\\_>\")))\
+;; Move up one level of parentheses.
+(defun go-goto-opening-parenthesis (&optional legacy-unused)
+ ;; The old implementation of go-goto-opening-parenthesis had an
+ ;; optional argument to speed up the function. It didn\'t change the
+ ;; function\'s outcome.
+
+ ;; Silently fail if there\'s no matching opening parenthesis.
+ (condition-case nil
+ (backward-up-list)
+ (scan-error nil)))
+
+
(defconst go-dangling-operators-regexp \"[^-]-\\\\|[^+]\\\\+\\\\|[/*&><.=|^]\")
(defconst go-identifier-regexp \"[[:word:][:multibyte:]]+\")
(defconst go-label-regexp go-identifier-regexp)\
@@ -289,18 +301,6 @@ curly brace we are checking. If they match, we return non-nil.\"\n (if (and (= (go-paren-level) start-nesting) (= old-point (point)))\n t))))))\n \n-(defun go-goto-opening-parenthesis (&optional char)\n- (let ((start-nesting (go-paren-level)))\n- (while (and (not (bobp))\n- (>= (go-paren-level) start-nesting))\n- (if (zerop (skip-chars-backward\n- (if char\n- (case char (?\\] \"^[\") (?\\} \"^{\") (?\\) \"^(\"))\n- \"^[{(\")))\n- (if (go-in-string-or-comment-p)\n- (go-goto-beginning-of-string-or-comment)\n- (backward-char))))))\n-\n (defun go--indentation-for-opening-parenthesis ()\n \"Return the semantic indentation for the current opening parenthesis.\n \n@@ -325,7 +325,7 @@ current line will be returned.\"\n ((go-in-string-p)\n (current-indentation))\n ((looking-at \"[])}]\")\n- (go-goto-opening-parenthesis (char-after))\n+ (go-goto-opening-parenthesis)\n (if (go-previous-line-has-dangling-op-p)\n (- (current-indentation) tab-width)\n (go--indentation-for-opening-parenthesis)))\n```
## コアとなるコードの解説
このコミットの主要な変更点は、`go-mode.el`内の`go-goto-opening-parenthesis`関数の定義が完全に置き換えられたことです。
1. **旧実装の削除**:
`go-goto-opening-parenthesis`の以前の定義(行289から301)が削除されました。この旧実装は、`go-paren-level`、`skip-chars-backward`、`go-in-string-or-comment-p`、`backward-char`といった関数を組み合わせて、手動で開き括弧を探索する複雑なロジックを持っていました。これは、Go言語の構文やEmacsのバッファ操作の細部に深く依存しており、保守が難しい可能性がありました。
2. **新実装の追加**:
新しい`go-goto-opening-parenthesis`関数が追加されました(行87から99)。この新実装は非常に簡潔です。
* `(condition-case nil (backward-up-list) (scan-error nil))`
この一行が関数の全てです。
* `backward-up-list`: Emacs Lispの組み込み関数であり、現在のカーソル位置から見て、対応する開き括弧(またはS式の開始)に移動します。この関数はEmacsの強力な構文解析エンジンを利用するため、Go言語の括弧構造を正確に解釈できます。
* `condition-case nil ... (scan-error nil)`: `backward-up-list`が対応する開き括弧を見つけられなかった場合(例えば、バッファの先頭に到達した場合や、構文エラーがある場合)に発生する`scan-error`を捕捉するためのエラーハンドリングです。`nil`を返すことで、エラーが発生しても関数が静かに終了し、カーソルが移動しないという挙動を実現しています。これにより、ユーザーは不必要なエラーメッセージを見ることなく、スムーズに操作を続行できます。
3. **呼び出し箇所の修正**:
`go--indentation-for-opening-parenthesis`関数内での`go-goto-opening-parenthesis`の呼び出し箇所も修正されています(行325)。以前は`go-goto-opening-parenthesis (char-after)`のように引数を渡していましたが、新しい`go-goto-opening-parenthesis`は引数を必要としないため、単に`go-goto-opening-parenthesis`と呼び出すように変更されました。`legacy-unused`引数は、既存の呼び出し元との互換性を保つために残されていますが、実際には使用されません。
この変更は、`go-mode.el`のコードベースをよりクリーンで、Emacs Lispの慣用的なスタイルに近づけるものです。組み込み関数を利用することで、Go言語の特定の構文解析ロジックを`go-mode.el`自身が持つ必要がなくなり、Emacsのコア機能にその責任を委譲しています。
## 関連リンク
* Emacs Lisp Manual: [https://www.gnu.org/software/emacs/manual/html_node/elisp/](https://www.gnu.org/software/emacs/manual/html_node/elisp/)
* Emacs Lisp `backward-up-list` documentation: Emacs内で`C-h f backward-up-list`と入力することで詳細なドキュメントを参照できます。
* Emacs Lisp `condition-case` documentation: Emacs内で`C-h f condition-case`と入力することで詳細なドキュメントを参照できます。
* Go言語のEmacsモード (`go-mode.el`) のソースコード: [https://github.com/golang/go/blob/master/misc/emacs/go-mode.el](https://github.com/golang/go/blob/master/misc/emacs/go-mode.el)
## 参考にした情報源リンク
* [https://github.com/golang/go/commit/6e37bc1eecb41838d9f7bdc27a5f323b1d92ff0e](https://github.com/golang/go/commit/6e37bc1eecb41838d9f7bdc27a5f323b1d92ff0e)
* [https://golang.org/cl/11524045](https://golang.org/cl/11524045) (Go Gerrit Code Review)
* Emacs Lispの公式ドキュメント (Emacs内でのヘルプ機能 `C-h f` や `C-h v` を利用)
* Emacs Lispに関する一般的な知識とプログラミング慣習。