Ace.js のシンタックスハイライターを自作する(実装編)


Ace.js は、Webページに簡単に埋め込むことができるテキストエディタです。様々なプログラミング言語に対して、その記述をサポートする機能(シンタックスハイライトや自動インデントなど)が用意されており、Webページ上でのプログラミング用エディタの作成に適しています。

この記事の目的

Ace.js には、様々な言語をサポートする機能が既に用意されています。しかし、もちろんすべての言語に用意されてはおらず、サポートに含まれない言語も存在します。特に、研究目的で作られたプログラミング言語のような、マイナーな言語はサポートされていません。ただ、Ace には言語サポート機能を自作できる仕組みが用意されています。この記事では、R-WHILE というプログラミング言語に対して、シンタックスハイライト機能を作成します。

実装編

環境構築編では、R-WHILEのシンタックスハイライト機能の実装に必要な mode-rwhile.js を作成するための環境を整えました。実装編では、rwhile_highlight_rules.js にシンタックスハイライト規則を記述し、実際に ace テキストエディタ上で R-WHILE のプログラムが色付けされるようにします。

シンタックスハイライト機能を実装する

R-WHILE の構文規則

R-WHILE の構文規則は次のようになります。

Variables   ∋ X, Y ::= X0 | X1 | ...
Expressions ∋ E, F ::= X | d | cons E F | hd E | tl E | =? E F
Patterns    ∋ Q, R ::= X | d | cons Q R
Commands    ∋ C, D ::= X ^= E | Q <= R | C; D
                      | if E then C else D fi F | from E do C loop D until F
Macro       ∋ M    ::= macro X ( X, ..., X ) C
Programs    ∋ P    ::= M* read X; C; write Y

Ace では、構文規則を厳密に定めることで、エラー表示など様々な恩恵を得ることができますが、この記事では、色付けのみ行います(すごく大変になるので)。ここで色付けするものは、macrocons などのキーワードと、上の規則には載っていませんが、'name で定義される変数です。

シンタックスハイライト規則を定義する

どのように色付けするのかが決まったので、実際に rwhile_highlight_rules.js にシンタックスハイライト規則を記述します。rwhile_highlight_rules.js は次のようになります。

rwhile_highlight_rules.js
...

var RwhileHighlightRules = function() {
  // キーワード
  var keyword = ("cons|hd|tl|if|fi|from|until|show|then|else|do|loop|read|write|macro");

  // 組み込み定数
  var builtinConstants = ("nil");

  var keywordMapper = this.createKeywordMapper({
    "keyword": keyword,
    "constant.language": builtinConstants,
  }, "identifier");

  // regexp must not have capturing parentheses. Use (?:) instead.
  // regexps are ordered -> the first match is used
  this.$rules = {
    "start": [
      {
        token: "comment", // multi line comment
        regex: "\\(\\*",
        next: "comment"
      }, {
        token: keywordMapper, // String, Array, or Function: the CSS token to apply
        regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b", // String or RegExp: the regexp to match
      }, {
        token: "keyword.operator",
        regex: "\\^=|<=|=\\?"
      }, {
        token: "variable",
        regex: /'(?!\s|\.|;|,|\))/,
        next: "variable"
      }
    ],
    "variable": [
      {
        token: "variable",
        regex: /[a-zA-Z0-9](?=\s|\.|;|,|\))/,
        next: "start"
      }, {
        token: "variable",
        regex: /^\s*/,
        next: "start"
      }, {
        defaultToken: "variable"
      }
    ],
    "comment": [
      {
        token: "comment", // closing comment
        regex: "\\*\\)",
        next: "start"
      }, {
        defaultToken: "comment"
      }
    ]
  };
}

解説

  • 初期状態がstartで、そこから条件(regex)に従ってnext(variable,comment)に遷移するオートマトンです。
  • tokenはCSSに関わります。例えばtoken:"comment"の場合、HTMLは以下のようになります。
<span class="ace_support ace_comment"> 文字列 </span>

ace_commentクラスに対応する色が読み込むテーマのCSSで定義されています。また、各テーマにおいてtextmateというテキストエディタの命名規則に従う限り、対応するカラーが定義されています。なので、Visual Studio CodeやAtomのシンタックスハイライト拡張機能を作るの記事を参考に、各状態のクラスを決めました。

結果

環境構築編の方法でmode-rwhile.jsを作成し、ace.jsで読み込むと、正しく色付けされていることがわかります。

補足

  • R-WHILE シンタックスハイライト機能のソースコードはこちらから見られます。

参考サイト