LoginSignup
57
61

More than 1 year has passed since last update.

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

Last updated at Posted at 2012-10-14

補完定義編 の続き(のつもり)です。この記事の内容は、Sublime Text ドキュメントの Syntax Definitionsを元ネタとしています。

事前準備

AAAPackageDev をインストールします。

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

JSON-tmLanguage の作成

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

***.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. の後に、言語の小文字の短縮名をつけた文字列とします。補完定義編で指定したものと同じものを使います。

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

fileTypes この構文を適用するファイルの拡張子を指定します。補完定義編で指定したものと同じものを使います。

patterns 後述します。

tmLanguage への変換

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

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

構文定義の意味

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

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

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

超単純なマッチパターン

基本形はこれ。

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

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

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

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

comment 構文パターンの説明。

マッチンググループを使ったマッチパターン

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

***.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 で定義したスコープだけが適用されます。

開始要素・終了要素があるマッチパターン

基本的に

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

name は同じです。

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

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

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

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

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

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

マッチの順番

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

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

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

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

で、どうやって色がつくの?

この答えとなるようなものは元ネタに載っていないため、TextMate のリファレンスを参考にします。

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

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

さらに細かく分かれているので、全部の説明はTextMate のリファレンスを参照してください。

57
61
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
57
61