Sublime Text2 言語パッケージ作成：構文定義


[補完定義編](http://qiita.com/items/02e7c4cd8b90288eb767) の続き（のつもり）です。この記事の内容は、[Sublime Text ドキュメントの Syntax Definitions](http://docs.sublimetext.info/en/latest/extensibility/syntaxdefs.html)を元ネタとしています。


## 事前準備
[AAAPackageDev](https://bitbucket.org/guillermooo/aaapackagedev) をインストールします。

構文定義ファイル自体は Plist という XML もどきの形式ですが、長ったらしくて書くのが面倒なため、JSON で書きます。AAAPackageDev は JSON で書いた構文定義を Plist に変換してくれるツールです。

## JSON-tmLanguage の作成

メニューから Tools > Packages > Package Development> New Syntax Definition とアクセスして、ファイルを作成します。エディタにこのように表示されているはずです。

```json:***.JSON-tmLanguage
{ "name": "Syntax Name",
  "scopeName": "source.syntax_name",
  "fileTypes": [""],
  "patterns": [
  ],
  "uuid": "9d286105-1dd9-464e-b197-3c24689d804e"
}
```

**name** エディタの右下に表示される名前です。このファイルを開いているときは、右下には *Sublime Text Syntax Definition* と表示されているはずです。この部分にここで指定した名前が入ります。

**scopeName** スコープの名称です。ソースファイルの場合は *source.* 、マークアップの場合は *text.* の後に、言語の小文字の短縮名をつけた文字列とします。[補完定義編](http://qiita.com/items/02e7c4cd8b90288eb767)で指定したものと同じものを使います。

**uuid** 構文を識別する一意名です。このファイルを生成するたびに変わります。手で書き換えることはありません。

**fileTypes** この構文を適用するファイルの拡張子を指定します。[補完定義編](http://qiita.com/items/02e7c4cd8b90288eb767)で指定したものと同じものを使います。


**patterns** 後述します。

## tmLanguage への変換
1. Tools > Build System で Json to tmLanguage を選択します。(メニューに表示されていない場合は、Sublime を再起動します)
2. ```F7``` キーを押します。
3. *.JSON-tmLanguage* ファイルと同じフォルダに*.tmLanguage* が作成されます。つまり、[補完定義編](http://qiita.com/items/02e7c4cd8b90288eb767)で作成した *.tmLanguage* が上書きされます。

ここで fileTypes で指定した拡張子で何かファイルを作って、補完定義編で定義したキーワードをタイプしてみます。無事に補完されていたら大丈夫です。

## 構文定義の意味
構文定義により、ファイルの中身がスコープに区切られます。スコープは次の用途で使われます。

* 構文の色づけ
* マクロやスニペットで、対象範囲を特定する

現在は、ファイル全体が scopeName で指定されたスコープです。対象のファイルを開き、エディタの適当な場所をクリックして、```command + alt+ p``` (または ```ctrl+ alt + shift+ p```) を押してみると、*source.構文名* と表示されるはずです。今から、構文パターンによって違う名前のスコープができるように pattern に追加していきます。

## 超単純なマッチパターン
基本形はこれ。

```json:***.JSON-tmLanguage
  "patterns": [
	{ "match": "\\$\\d+",
      "name": "keyword.source.syntax_name",
      "comment": "$1, $2... みたいな感じ"
	}
  ],
```

この例では、"\\$\\d+" パターンにあてはまる部分の文字列が keyword.source.syntax_name スコープとなります。

**match** 構文パターンの正規表現。JSON 向けにエスケープして書きます。

**name** この構文パターンを識別する名前。

**comment** 構文パターンの説明。

## マッチンググループを使ったマッチパターン
もうちょっと複雑にして、パターンにマッチンググループを入れてみます。カッコでくくると、マッチンググループとなります。

```json:***.JSON-tmLanguage
  "patterns": [
	{ "match": "\\$([A-Za-z][A-Za-z0-9_]+)",
      "name": "keyword.syntax_name",
      "captures": {
      "1": { "name": "constant.numeric.syntax_name" }
       },
      "comment": "$PARAM1, $TM_SELECTION... みたいな感じ"
    }
  ],
```

この例では、例えば $PARAM1 の場合、$PARAM1 全体が *keyword.syntax_name* スコープとなり、さらに PARAM1 の部分に *constant.numeric.syntax_name* スコープが追加されます。

**captures** マッチンググループごとのスコープ定義。巷の言語の正規表現と同じく、1から始まります。match パターンにマッチンググループがあっても、ここでスコープを定義してなければ、name で定義したスコープだけが適用されます。

## 開始要素・終了要素があるマッチパターン
基本的に

```json:構文
{ "name": "",
  "begin": "",
  "beginCaptures": {
    "0": { "name": "" }
  },
  "end": "",
  "endCaptures": {
    "0": { "name": "" }
  },
  "patterns": [
     {  "name": "",
        "match": ""
                  }
  ],
  "contentName": ""
}
```

**name** は同じです。

**begin**、**end** には構文の開始終了パターン(開始終了タグのようなもの)を定義します。

マッチンググループを作った場合、それぞれ、**beginCaptures**、**endCaptures** でグループごとのスコープを定義できます。

**patterns** は開始終了パターン以外の部分のマッチパターンです。後述。

**contentName** には patterns の部分に適用されるスコープの名前を定義します。(元ネタは開始終了パターンを含むと書いてありますが、間違いでは？)

例えばこのパターンで ${12:${34:TEST}} を解析すると、次のようになります。

```json:***.JSON-tmLanguage
   "patterns": [
       { "name": "variable.complex.syntax_name",
          "begin": "(\\$)(\\{)([0-9]+):",
          "beginCaptures": {
              "1": { "name": "keyword.syntax_name" },
              "3": { "name": "constant.numeric.syntax_name" }
           },
           "patterns": [
              { "include": "$self" },
              { "name": "string.syntax_name",
                "match": "."
              }
           ],
           "end": "\\}",
           "comment": "${12:${34:TEST}} … みたいな感じ"
        },
  ],
```

1. 全体のスコープとして variable.complex.syntax_name が割り当てられます。
2. ${12: と、${34:TEST} と、} に３分割されます。
3. beginCaptures により、$ の部分には keyword.syntax_name スコープが割り当てられ、12 の部分には constant.numeric.syntax_name が割り当てられます。
4. ${34:TEST} の解析が始まりますが、その前に ```"include": "$self"``` により、このパターンに再帰的に当てはめられます。つまり、2. の手順で３分割され、3. の手順で $ と 34 にそれぞれスコープが割り当てられます。
5. 残った文字列 TEST に、```"include": "$self"`` でまたマッチング処理が行われますが、今回はマッチしません。このため、次の **match** のマッチング処理が行われ、（必ずヒットするので）この文字列に **name** で定義された string.syntax_name が割り当てられます。

## マッチの順番
patterns に書かれている中で、上から順番にマッチされていくため、既にマッチされた部分の文字列は除外されていきます。このため、次のような書き方で、エスケープしていない記号を検出できます。

```json:***.JSON-tmLanguage
   "patterns": [
        { "name": "constant.character.escape.syntax_name",
          "match": "\\\\(\\$|\\>|\\<)"
        },

        { "name": "invalid.ssraw",
          "match": "(\\$|\\>|\\<)"
        }
  ],
```

これまでの説明で一通りの構文パターンが書けます。TextMate のマニュアルを見ると、更に省力化して書く方法も載ってはいますが。

## で、どうやって色がつくの？
この答えとなるようなものは元ネタに載っていないため、[TextMate のリファレンス](http://manual.macromates.com/en/language_grammars)を参考にします。

スコープの名前の先頭が次の文字で始まっていれば、Sublime の機能によって自動的にそれなりの色がつきます。

* **comment.** コメント
* **constant.** 定数
* **entity.** クラスとか、ファンクションとか、タグとかの、ちょっと大きめの単位
* **invalid.** けしからんもの（どす黒い赤のマーカーがつく）
* **keyword.** キーワード
* **markup.** マークアップ文書のテキスト
* **meta.** 関数定義など (色がつくことはほとんどないので、単純に操作範囲を狭めたいときに利用)
* **storage.** 型とか修飾子とか
* **string.** 文字列
* **support.** (言語機能でなく)フレームワークで提供されるもの
* **variable.** 変数名

さらに細かく分かれているので、全部の説明は[TextMate のリファレンス](http://manual.macromates.com/en/language_grammars)を参照してください。