はじめに
Visual Studio CodeやAtomに言語のシンタックスハイライトを加えるにはtextmateというエディターが採用している記法で構文を定義します。
なぜtextmateの方法が採用されているのかは知らないのですが,どういうわけか広く利用されているようです。
構文を定義する
textmate的構文定義は
.tmLanguage
.json
.plist
など様々な方法で記述できるようです。
個人的にはJSON版が扱いやすそうです。
英語ですが公式のドキュメントがあるので,重要そうな部分だけを説明します。
概要
構文は再帰的に定義できます。
あるブロックを認識してそのブロックは3つの構文に分解され最初の構文は別の構文によって処理される...のように記述できます。
定数を数値定数と文字列定数に分けるなど,できるだけ細かい単位で構文を作っておくと
include
機能を使って使い回しが効きますし,見通しがよくなります。
キーの説明
様々なキーを使って構文を定義します。
name
命名規則に従った名前をつけることで,「ここは関数名である」「ここはコメントである」「ここは文字列定数である」ということを指定します。Visual Studio Codeのようなエディタはこの情報をもとにしてテーマから指定に相当する色を決定し,シンタックスハイライト(色分け)を行うというわけです。
nameの命名規則
箇条書きで説明を書いていきますが,実際に使うのは箇条書きの項目をドット.
で連結したものです。
たとえば
- constant
- numeric
となっていたら"name": "constant.numeric"
とします。また全てを網羅しているわけではないので公式のドキュメントの下の方も合わせてご覧ください。
-
comment
- line
- double-dash
//
で始まるコメントを表します
- double-dash
- block
/* */
で囲むようなコメントブロックを表します。
- line
-
constant
- numeric
数値の定数を表します。 例:42
- language
その言語で特別に使う語を表します。 例:true, nill
- numeric
-
entity
- name
- function
関数定義における関数の名前を表します。関数を呼び出すときのものではありません。
- function
- name
-
invalid
- illegal
その言語においてありえない記述を表します。JSONでキーがダブルクォーテーションで囲われていない場合などに使います。
- illegal
-
keyword
- control
continue, while, return
などフローの制御に使う語を表します。 - operator
演算子を表します。
- control
-
markup
- italic
イタリック体にしたい箇所に使います。
- italic
-
storage
- type
クラスや関数定義のclass
やfunction
の部分に使います。 - modifiers
static, final, abstract
などに使います。
- type
-
string
- quoted
- double
ダブルクォーテーション"
で囲われた文字列を表します。
- double
- regexp
正規表現を表します。例:/[0-9]+/
- quoted
-
support
フレームワークやライブラリで提供されるものはsupportで表現するようです。- function
関数の呼び出しにおける関数名を表します。
entity.name.function
との違いに注意。
- function
-
variable
- parameter
パラメーターとして宣言された変数を表します。 - language
その言語で予約されている変数を表します。例:this, super, self
- parameter
match
正規表現でマッチさせたいパターンを入れます。
例(\\b(true|false)\\b)
patterns
マッチさせるパターンの候補(複数可)です。
上から順にマッチングが試行されます。
パターンが1つの場合は1つだけでよいです。
captures
matchで()
で囲うことによりグループを作った場合,
そのグループごとについてさらにパターンを適用できます。
やってみよう
ここまで長々と書きましたが,実際の例を見た方が分かりやすいと思います。
上で説明していないキーなどが登場しますが雰囲気はつかめると思います。
ここでは以下の変数宣言の構文定義の例を挙げます。
var count: int = 10;
スタート地点は変数定義(var_declaration)です。
変数定義の中で(\\S+)\\s+(\\S+):\\s+(\\S+)\\s+=\\s+(\\S+);
という正規表現で4つの部分(var,変数名,変数の型,定数)に分けています。
例えば先頭のvarはname
にstorage.type.example
を指定しています。
また,最後の定数は数値定数(constant_number)と文字列定数(constant_string)で認識を試行します。
例の場合最後の定数は10なので数値定数で認識されることになります。数値定数のname
にはconstant.numeric.example
を指定しています。
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "",
"patterns": [
{
"include": "#var_declaration"
}
],
"repository": {
"var_declaration": {
"patterns": [
{
"match": "(\\S+)\\s+(\\S+):\\s+(\\S+)\\s+=\\s+(\\S+);",
"captures": {
"1": {"patterns": [{"include": "#var"}]},
"2": {"patterns": [{"include": "#var_name"}]},
"3": {"patterns": [{"include": "#var_type"}]},
"4": {"patterns": [{"include": "#constant"}]}
}
}
],
"repository": {
"var": {
"patterns": [
{
"name" : "storage.type.example",
"match": "var"
}
]
},
"var_name": {
"patterns": [
{
"name": "variable.other.example",
"match": "[0-9a-zA-Z]+"
}
]
},
"var_type": {
"patterns": [
{
"name": "storage.type.example",
"match": "\\b(int|double|float)\\b"
}
]
},
"constant": {
"patterns": [
{"include": "#constant_number"},
{"include": "#constant_string"}
],
"repository": {
"constant_number": {
"patterns": [
{
"name": "constant.numeric.example",
"match": "-?[0-9]+"
}
]
},
"constant_string": {
"patterns": [
{
"name": "constant.character.example",
"begin": "\"",
"end": "\""
}
]
}
}
}
}
}
},
"scopeName": "source.example"
}
ちなみに結果はこんな感じになります。きれいに色がついていますね。
参考リンク
Sublime Text2 言語パッケージ作成:構文定義
textmane manual - 12. Language Grammars