Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18975] ファイルの概要

このコミットは、EmacsのGoモード(go-mode.el)における、Go言語の生文字列リテラル(raw string literal)の構文解析とハイライトに関する問題を修正するものです。特に、Emacs 23のような古いバージョンで、生文字列リテラル内のバックスラッシュが正しく認識されないために発生する表示の不具合に対処しています。

コミット

commit 444dd26bf4c585ce71dfcd9e581865ba3047dc75
Author: Rui Ueyama <ruiu@google.com>
Date:   Thu Mar 27 15:22:52 2014 -0400

    misc/emacs: handle backslash in raw string in Emacs 23
    
    Go-mode in Emacs 23 does not recognize a backslash followed
    by a backquote as end of raw string literal, as it does not
    support syntax-propertize-function which Go-mode uses to
    remove special meaning from backslashes in ``.
    
    This patch provides a fallback mechanism to do the same thing
    using font-lock-syntactic-keywords, which is supported by
    Emacs 23.
    
    LGTM=dominik.honnef
    R=golang-codereviews, dominik.honnef
    CC=adonovan, golang-codereviews
    https://golang.org/cl/78730048

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/444dd26bf4c585ce71dfcd9e581865ba3047dc75

元コミット内容

このコミットは、Emacs 23環境でGoモードを使用する際に発生する、生文字列リテラル(バッククォート `` で囲まれた文字列)の構文解析の不具合を修正することを目的としています。具体的には、Emacs 23のGoモードが、生文字列リテラル内のバックスラッシュ(\)の特殊な意味を正しく取り除けないという問題がありました。これは、Goモードが通常使用するsyntax-propertize-functionがEmacs 23でサポートされていないためです。この結果、生文字列リテラル内にバックスラッシュとバッククォートが連続して出現した場合に、Emacsがそれを生文字列リテラルの終了と誤認識してしまう可能性がありました。

変更の背景

Go言語の生文字列リテラルは、バッククォートで囲まれ、内部の文字がすべて文字通りに解釈されるという特性を持っています。これにはバックスラッシュも含まれ、エスケープシーケンスとして扱われることはありません。しかし、EmacsのGoモードは、構文ハイライトのためにsyntax-propertize-functionという機能を利用して、このような生文字列リテラル内のバックスラッシュが特殊な意味を持たないように処理していました。

問題は、Emacs 23がこのsyntax-propertize-functionをサポートしていなかった点にあります。そのため、Emacs 23でGoモードを使用すると、生文字列リテラル内のバックスラッシュが誤ってエスケープ文字として解釈され、その後に続くバッククォートが生文字列リテラルの終了を示すものと誤認される可能性がありました。これにより、構文ハイライトが崩れたり、コードの編集体験が悪化したりする問題が発生していました。

このコミットは、Emacs 23のような古いバージョンでもGoモードが正しく機能するように、syntax-propertize-functionが利用できない場合のフォールバックメカニズムを提供することで、この問題を解決しようとしています。

前提知識の解説

Go言語の生文字列リテラル (Raw String Literals)

Go言語には、二重引用符(")で囲む「解釈済み文字列リテラル (interpreted string literal)」と、バッククォート(``)で囲む「生文字列リテラル (raw string literal)」の2種類の文字列リテラルがあります。

  • 解釈済み文字列リテラル: バックスラッシュ(\)がエスケープ文字として機能し、\nは改行、\tはタブ、\\はバックスラッシュ自体を表します。
  • 生文字列リテラル: バッククォートで囲まれた内部の文字は、すべて文字通りに解釈されます。バックスラッシュも特殊な意味を持たず、エスケープシーケンスとして処理されません。複数行にわたる文字列を記述するのに便利で、正規表現、HTML、SQLクエリなどをコードに埋め込む際によく使用されます。生文字列リテラル自体の中にバッククォートを含めることはできません。

例:

// 解釈済み文字列リテラル
interpretedString := "Hello\\nWorld" // "Hello\nWorld" となる

// 生文字列リテラル
rawString := `C:\path\to\file` // "C:\path\to\file" となる
multiLineRawString := `
This is a
multi-line
raw string.
`

EmacsのFont Lockモードと構文ハイライト

EmacsのFont Lockモードは、プログラミング言語やテキストファイルの構文を解析し、キーワード、コメント、文字列などを異なる色やフォントで表示する機能です。これにより、コードの可読性が向上し、構文エラーの発見にも役立ちます。

Font Lockモードは、主に以下のメカニズムを使用して構文ハイライトを行います。

  • Syntax Table (構文テーブル): 各文字の構文的な役割(単語構成文字、区切り文字、文字列区切り文字など)を定義します。
  • font-lock-keywords: 正規表現のリストで、特定のパターンに一致するテキストをハイライトするために使用されます。
  • syntax-propertize-function: より複雑な、文脈依存の構文解析を行うための強力なメカニズムです。この関数は、バッファの特定の領域に対してsyntax-tableテキストプロパティを適用し、デフォルトの構文テーブルのルールを上書きすることができます。これにより、複数行にわたる文字列やコメント、ネストされた構造など、単純な正規表現では対応しきれない構文を正確に解析し、ハイライトすることが可能になります。Emacs 24.1以降で推奨される構文解析方法です。
  • font-lock-syntactic-keywords: syntax-propertize-functionが登場する以前のEmacsバージョン(Emacs 23など)で、文脈依存の構文ハイライトを行うために使用されていたメカニズムです。これも正規表現と関連する構文プロパティのリストを使用しますが、syntax-propertize-functionほど柔軟性や強力さはありません。Emacs 24.1以降では非推奨(obsolete)となっています。

このコミットの背景にある問題は、Goモードがsyntax-propertize-functionを使用して生文字列リテラル内のバックスラッシュの特殊な意味を取り除いていたにもかかわらず、Emacs 23がこの機能をサポートしていなかったために、その処理が機能しなかったことにあります。

技術的詳細

このコミットの技術的な核心は、Emacsのバージョンに応じた構文解析メカニズムの切り替えと、古いEmacsバージョン(Emacs 23)向けのフォールバックの実装です。

Goモードは、通常、syntax-propertize-functiongo-propertize-syntax関数に設定することで、Go言語の複雑な構文(特に生文字列リテラル内のバックスラッシュの扱い)を正確に解析していました。syntax-propertize-functionは、バッファのテキストにsyntax-tableプロパティを適用することで、Emacsの構文解析エンジンに特定の文字の構文的な意味を上書きさせることができます。これにより、生文字列リテラル内のバックスラッシュがエスケープ文字としてではなく、単なる文字として扱われるように指示していました。

しかし、Emacs 23ではsyntax-propertize-functionが利用できません。このコミットは、boundp 'syntax-propertize-functionというEmacs Lispの関数を使って、現在のEmacs環境でsyntax-propertize-functionが定義されているかどうかをチェックします。

  • もしsyntax-propertize-functionが利用可能であれば(Emacs 24.1以降など)、これまで通りgo-propertize-syntaxを設定します。
  • もしsyntax-propertize-functionが利用できない場合(Emacs 23など)、フォールバックとしてfont-lock-syntactic-keywordsを使用します。

フォールバックメカニズムでは、go--font-lock-syntactic-keywordsという新しい定数が導入されています。この定数には、生文字列リテラルを識別するための正規表現と、その内容の構文プロパティを上書きするためのルールが定義されています。具体的には、生文字列リテラル全体をfont-lock-multilineとしてマークし、その内部のバックスラッシュが特殊な意味を持たないようにsyntax-tableプロパティをnil(汎用文字列)に設定します。これにより、Emacs 23でも生文字列リテラル内のバックスラッシュが正しく解釈され、構文ハイライトの不具合が解消されます。

また、font-lock-multiline変数をtに設定することで、複数行にわたる生文字列リテラルも正しくハイライトされるようにしています。

コアとなるコードの変更箇所

misc/emacs/go-mode.el ファイルが変更されています。

--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -272,6 +272,7 @@ For mode=set, all covered lines will have this weight."
      `((,go-func-meth-regexp 2 font-lock-function-name-face))) ;; method name
 
    `(
+     ("\\(`[^`]*`\\)" 1 font-lock-multiline) ;; raw string literal, needed for font-lock-syntactic-keywords
      (,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]+\\([^[:space:]]+\\)") 1 font-lock-type-face) ;; types
      (,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]+" go-identifier-regexp "[[:space:]]*" go-type-name-regexp) 1 font-lock-type-face) ;; types
      (,(concat "[^[:word:][:multibyte:]]\\[\\([[:digit:]]+\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 2 font-lock-type-face) ;; Arrays/slices
@@ -290,6 +291,14 @@ For mode=set, all covered lines will have this weight."
      (,(concat "^[[:space:]]*\\(" go-label-regexp "\\)[[:space:]]*:\\(\\S.\\|$\\)") 1 font-lock-constant-face) ;; Labels and compound literal fields
      (,(concat (go--regexp-enclose-in-symbol "\\(goto\\|break\\|continue\\)") "[[:space:]]*\\(" go-label-regexp "\\)") 2 font-lock-constant-face)))) ;; labels in goto/break/continue
 
+(defconst go--font-lock-syntactic-keywords
+  ;; Override syntax property of raw string literal contents, so that
+  ;; backslashes have no special meaning in ``. Used in Emacs 23 or older.
+  '(("\\(`\\)\\(.*\\)\\(```\\)"
+     (1 (7 . ?`))
+     (2 (15 . nil))  ;; 15 = "generic string"
+     (3 (7 . ?`))))))
+
 (defvar go-mode-map
   (let ((m (make-sparse-keymap)))\n     (define-key m "}" #'go-mode-insert-and-indent)\n@@ -564,7 +573,10 @@ recommended that you look at goflymake\n \n   (set (make-local-variable 'parse-sexp-lookup-properties) t)\n   (if (boundp 'syntax-propertize-function)\n-      (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax)\n+      (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax)\n+    (set (make-local-variable 'font-lock-syntactic-keywords)\n+         go--font-lock-syntactic-keywords)\n+    (set (make-local-variable 'font-lock-multiline) t))\n \n   (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))\n   (add-hook 'before-change-functions (lambda (x y) (setq go-dangling-cache (make-hash-table :test 'eql))) t t)\n```

## コアとなるコードの解説

1.  **`("\\(`[^`]*`\\)" 1 font-lock-multiline)` の追加**:
    *   これは`font-lock-keywords`リストに追加された新しい正規表現です。
    *   `("\\(`[^`]*`\\)")` は、バッククォートで始まり、バッククォート以外の任意の文字が0回以上続き、バッククォートで終わるパターン、つまりGoの生文字列リテラル全体に一致します。
    *   `1 font-lock-multiline` は、このパターンに一致するテキスト全体を`font-lock-multiline`としてハイライトするように指示しています。これは、複数行にわたる生文字列リテラルが正しくハイライトされるために必要です。

2.  **`go--font-lock-syntactic-keywords` 定数の定義**:
    *   この新しい定数は、Emacs 23のような古いバージョンで`font-lock-syntactic-keywords`を使用するためのルールを定義しています。
    *   `'("\\(`\\)\\(.*\\)\\(```\\)" ...)`: この正規表現は、生文字列リテラルを3つのグループに分割します。
        *   グループ1: 最初のバッククォート (`(`)
        *   グループ2: 生文字列リテラルの内容 (`.*`)
        *   グループ3: 最後のバッククォート (```)
    *   `(1 (7 . ?`))`: グループ1(最初のバッククォート)の構文プロパティを、文字通りバッククォートとして解釈されるように設定します。
    *   `(2 (15 . nil))`: グループ2(生文字列リテラルの内容)の構文プロパティを`nil`に設定します。`15`はEmacsの内部的な構文コードで「汎用文字列」を意味します。これにより、生文字列リテラル内のバックスラッシュがエスケープ文字としてではなく、単なる文字として扱われるようになります。
    *   `(3 (7 . ?`))`: グループ3(最後のバッククォート)の構文プロパティを、文字通りバッククォートとして解釈されるように設定します。

3.  **`if (boundp 'syntax-propertize-function)` ブロックの変更**:
    *   このブロックは、`syntax-propertize-function`が現在のEmacs環境で利用可能かどうかをチェックします。
    *   **変更前**: `syntax-propertize-function`が利用可能であれば、`go-propertize-syntax`を設定していました。
    *   **変更後**:
        *   `syntax-propertize-function`が利用可能であれば、引き続き`go-propertize-syntax`を設定します。
        *   `else` ブロックが追加され、`syntax-propertize-function`が利用できない場合(Emacs 23など)のフォールバック処理が記述されています。
            *   `(set (make-local-variable 'font-lock-syntactic-keywords) go--font-lock-syntactic-keywords)`: `font-lock-syntactic-keywords`を、新しく定義された`go--font-lock-syntactic-keywords`に設定します。これにより、Emacs 23でも生文字列リテラルの構文が正しく解析されるようになります。
            *   `(set (make-local-variable 'font-lock-multiline) t)`: `font-lock-multiline`変数を`t`に設定します。これは、`font-lock-syntactic-keywords`が複数行にわたる構文を正しく処理するために必要です。

これらの変更により、GoモードはEmacs 23のような古いバージョンでも、Goの生文字列リテラル内のバックスラッシュを正しく処理し、構文ハイライトの不具合を解消できるようになりました。

## 関連リンク

*   Go言語の仕様: [https://go.dev/ref/spec#String_literals](https://go.dev/ref/spec#String_literals)
*   Emacs Lisp Reference Manual: Font Lock Mode: [https://www.gnu.org/software/emacs/manual/html_node/elisp/Font-Lock-Mode.html](https://www.gnu.org/software/emacs/manual/html_node/elisp/Font-Lock-Mode.html)
*   Emacs Lisp Reference Manual: Syntax Properties: [https://www.gnu.org/software/emacs/manual/html_node/elisp/Syntax-Properties.html](https://www.gnu.org/software/emacs/manual/html_node/elisp/Syntax-Properties.html)

## 参考にした情報源リンク

*   Google Search for "Go-mode Emacs 23 raw string backslash"
*   Google Search for "Emacs syntax-propertize-function"
*   Google Search for "Emacs font-lock-syntactic-keywords"
*   Google Search for "Go raw string literal"
*   Google Search for "Go-mode Emacs"
*   https://www.gnu.org/software/emacs/manual/html_node/elisp/Syntax-Properties.html
*   https://www.gnu.org/software/emacs/manual/html_node/elisp/Font-Lock-Mode.html
*   https://go.dev/ref/spec#String_literals
*   https://dev.to/siddhant_goel/emacs-go-mode-setup-2021-311k
*   https://www.emacswiki.org/emacs/GoMode
*   https://stackoverflow.com/questions/17528810/what-is-the-difference-between-raw-string-literals-and-interpreted-string-liter