highlight.js - にほんご。

Language definition guide

ハイライトの概要

プログラムコードは、構文解析ルールが異なるパーツで構成されています。 forifのようなキーワードは、文字列の中で意味を持たないので、 文字列に\"のようなバックスラッシュでエスケープされた文字列が含まれることがあります。 また、コメントには通常、コメントの最後の部分を除いて、重要な要素はありません。

highlight.jsではこのようなパーツを"モード"と呼んでいます。

各モードは以下の条件で成り立っています。

  • 開始条件
  • 終了条件
  • 含まれているサブモードのリスト
  • 字句解析のルールと、キーワード
  • 言語の中に別の言語が含まれるような変わったもの

パーサの仕事は、モードとキーワードを探すことです。 モードやキーワードを見つけると、<span class="...">...</span>でマークアップします。 そして、モード名(stringcommentnumber)か、 キーワードのグループ名(keywordliteralbuilt-in)をクラス名として追加します。


一般的な構文

言語定義は、言語の"デフォルト「解析」モード"を記述したJavaScriptのオブジェクトです。 このデフォルトモードは、他のサブモードを含んだサブモードを持っており、 効果的に言語定義をモードのツリーにします。

以下に例を示します。

{
  case_insensitive: true, // 大文字・小文字を区別しない言語です。
  keywords: 'for if while',   // キーワードを定義します。
  contains: [
    {
      className: 'string',
      begin: '"', end: '"'  // "(ダブルクオーテーション)で囲まれた文字をstring(文字列)とします。
    },
    hljs.COMMENT(
      '/\\*', // コメントの開始
      '\\*/', // コメントの終了
      {
        contains: [
          {
            className: 'doc', begin: '@\\w+'  // @と英単語で始まる場合はdocとします。
          }
        ]
      }
    )
  ]
}

通常、デフォルトモードはコードの大半を占め、言語のキーワードを全て記述します。 XMLについては例外で、デフォルトモードにキーワードを含まず、 ユーザーテキストのみで構成されます。 そして、パースはタグの中で発生します。


キーワード

単純な場合、言語のキーワードはスペースで区切られた文字列で定義します。

{
  keywords: 'else for if while'
}

いくつかの言語には別の種類の「キーワード」があります。 例えば、あらゆる種類の"リテラル"、"ビルトイン"、"シンボル"等です。 言語使用によっては、キーワードとは呼ばないかもしれませんが、 シンタックスハイライトの観点からすれば、とても近いものです。 そのようなキーワードをグループを定義するために、 keywords属性に、それぞれのプロパティの独自のキーワードグループを定義します。

{
  keywords: {
    keyword: 'else for if while',
    literal: 'false true null'
  }
}

グループ名は、マークアップ時に使用するクラス名になるため、 キーワードグループごとに別のスタイルを適用することができます。

highlight.jsは、キーワードを検出すると、処理されたコードを別々の「単語」に分解します。 この処理をlexing(レクシング=字句解析)と言います。 ここでの「単語」は、正規表現の[a-zA-Z][a-zA-Z0-9_]*で定義された文字列で、ほとんどの言語のキーワードに対応しています。 別の字句解析ルールは、lexemes属性で定義します。

{
  lexemes '-[a-z]+',
  keywords: '-import -export'
}

サブモード

サブモードはcontains属性の中にリスト化して定義します。

{
  keywords: '...',
  contains: [
    hljs.QUOTE_STRING_MODE,
    hljs.C_LINE_COMMENT,
    { ... custom mode definition ... }
  ]
}

モードは、特殊なキーワードのselfを使って、 contains配列内から自身を参照することができます。 これはネストしたモードの定義によく使われます。

{
  className: 'object',
  begin: '{', end: '}',
  contains: [hljs.QUOTE_STRING_MODE, 'self']
}

コメント

カスタムコメントを定義する場合、モードを直接記述するのではなく、 ビルトイン関数のhljs.COMMENTを使うことをお勧めします。 これは、言語検出を改善し、他にも良いことをしてくれるデフォルトのサブモードが用意されているためです。

関数のパラメータは以下のとおりです。

hljs.COMMENT(
  begin,      // コメント開始の正規表現
  end,        // コメント終了の正規表現
  extra       // デフォルト値を上書きします。オプションです。
              // (例: {relevance: 0})
)

マークアップ

モードは通常、ハイライト済みのマークアップを生成します。 (className属性で定義されたクラスを持ったspan要素)

{
  contains: [
    {
      className: 'string',
      // ... 他の属性
    },
    {
      className: 'number',
      // ...
    }
  ]
}

classNameは一意である必要なく、 一般的には、同名の定義が複数あることが多いでしょう。 例えば、多くの言語には、文字列、コメント等、様々な構文があります。

モードは構文解析ルールをサポートするために定義し、 マークアップには不要な場合があります。 古典的な例として、 エスケープシーケンスを使うことで、終了引用符を文字列内に含めることができます。

{
  className: 'string',
  begin: '"', end: '"',
  contains: [{begin: '\\\\.'}],
}

そのようなモードの場合、不要なマークアップを生成しないよう、 className属性を省略する必要があります。


モード属性

その他の便利な属性は、モードリファレンスで定義されています。


コードの関連性

highlight.jsは、コードから言語を自動で検出しようとします。 ヒューリスティックは実に単純です。 該当した言語定義を全てハイライトしようとし、 最も適切なモードやキーワードが適用されます。 言語定義の仕事は、モードの相対的な関連性(or 無関連性)について、 ヒューリスティックを手助けすることです。

よく説明で使われるのは次の例です。 Pythonには、r"..."や、u"..."のように、 引用符の前にプレフィックスがある特殊な文字列があります。 コードにこのような文字列がある場合、それがPythonである可能性が高いと考えられます。 そのため、上記の文字列モードには、高い関連性があると言えます。

{
  className: 'string',
  begin: 'r"', end: '"',
  relevance: 10   // 関連性を設定
}

一方、シングルクォーテーションや、ダブルクオーテーションで囲まれた文字列はどの言語にも当てはまるため、 特定言語の固有の表現ではなく、モードの関連性を無くすことは、統計的なノイズを減らす意味があります。

{
  className: 'string',
  begin: '"', end: '"',
  relevance: 0    // 関連性を無くす
}

relevanceのデフォルト値は1です。 relevanceを明示的に設定する場合、 10または0を指定することをお勧めします。

キーワードもまた関連性に影響します。 通常キーワードは、それぞれの関連性は1ですが、 変数名の形式であっても、その言語以外では見られない固有の名前がいくつかあります。 例えば、コードのどこかにreinterpret_castを置くだけで、 そのコードがC++であると判断できる良い指標になります。 こういったキーワードには、関連性を少し高く設定する価値があります。 キーワードへの関連性はパイプを使って設定します。

{
  keywords: 'for if reinterpret_cast|10'
}

不正シンボル

言語検出を向上させる別の方法は、モードに対して不正シンボルを定義することです。 例えば、Pythonのクラス定義(class MyClass(object):)の1行目には、 {や、改行(\n)を含めることはできません。 これらのシンボルが存在するならば、言語がPythonでないことが明確なので、 Pythonであるかどうかという言語検出を早い段階で打ち切ることができます。

不正シンボルは、単一の正規表現で定義します。

{
  className: 'class',
  illegal: '[${]'
}

定義済みのモードと正規表現

多くの言語が共通のモードと、正表現を共有しています。 これらは、highlight.jsコードの最後の、 “Common regexps”と“Common modes”というコメントの以下に記述されています。 できれば使ってください。

© 2006 Highlight.js is released under the BSD License. See LICENSE file for details.

このコンテンツはhighlightjsドキュメントを翻訳/改変したものです。