Qiita に J 言語のシンタックスハイライトがないので自分で実装した

# はじめに

最近 [J 言語](https://www.jsoftware.com) にハマっているのですが、残念なことに Qiita のシンタックスハイライトは J をサポートしてくれていません :qiitan-cry: 。~~J は特に見た目がアレな言語なので、~~ 記事を書く側も読む側もシンタックスハイライトがあるとモチベーションが上がると思います。

ということで、自分で実装することにしました。その記録を、ここに残しておこうと思います。

他の言語のシンタックスハイライトを実装する時に参考になるかもしれませんし、J 言語が他の言語と違いすぎてあまり参考にならないかもしれません。

# 大まかな手順

Qiita のシンタックスハイライトには現在 [Rouge](https://github.com/rouge-ruby/rouge) という Ruby 製のライブラリが使われています。このライブラリにプルリクエストを送ることで、J のハイライトを追加しようと考えました。

Rouge をフォークしたら、[lexer 開発のガイド](https://rouge-ruby.github.io/docs/file.LexerDevelopment.html) を見ながら始めます。

最初に、lexer を定義するファイルと、spec を追加します。

```rb:lib/rouge/lexers/j.rb
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
  module Lexers
    class J < RegexLexer
      title 'J'
      desc "The J programming language (www.jsoftware.com)"
      tag 'j'
      filenames '*.ijs', '*.ijt'

      # ここに lexer の実装を書く
    end
  end
end
```

```rb:spec/lexers/j_spec.rb
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

describe Rouge::Lexers::J do
  let(:subject) { Rouge::Lexers::J.new }

  describe 'guessing' do
    include Support::Guessing

    it 'guesses by filename' do
      assert_guess :filename => 'foo.ijs'
      assert_guess :filename => 'foo.ijt'
    end
  end

  describe 'lexing' do
    include Support::Lexing

    # ここにテストを書く
  end
end
```

この他に `lib/rouge/demos/j` と `spec/visual/samples/j` が必要ですが、始めは空のファイルで構いません。

- [lib/rouge/lexers/j.rb](https://github.com/rouge-ruby/rouge/blob/master/lib/rouge/lexers/j.rb)
- [spec/lexers/j_spec.rb](https://github.com/rouge-ruby/rouge/blob/master/spec/lexers/j_spec.rb)
- [spec/visual/samples/j](https://github.com/rouge-ruby/rouge/blob/master/spec/visual/samples/j)
- [lib/rouge/demos/j](https://github.com/rouge-ruby/rouge/blob/master/lib/rouge/demos/j)

あとは、以下の手順を並行して行います。言葉で解説するより実際のコードを見た方が早いので、細かい説明は省きます。知っている/よく使う言語の lexer を見れば大体分かると思います。

## spec を書く

RSpec の書き方を知っていれば、特に困ることはないと思います [^1]。

テストには `assert_tokens_equal` を使います。

```
assert_tokens_equal "コード", トークン1, トークン2, ...
```

トークンは、`[名前, テキスト]` の組で表します。トークンの名前については、[一覧](https://github.com/rouge-ruby/rouge/wiki/List-of-tokens) を参照してください。

実際のところ spec がほとんど書かれていない言語も多いようなので、*あまり詳細に書く必要はないのかもしれません*。

## lexer を書く

lexer の記述にも、DSL(EDSL) が使われています。

```
state シンボル do
  rule 正規表現, トークン
  ...
end
```

詳しいことはここでは説明しません。分からないときは他の言語の lexer を見ると参考になると思います [^2]。

## visual sample / demo を書く

visual sample (`spec/visual/samples/j`) は、正しくハイライトされるかを、目で見てチェックするためのテキストファイルです。ある程度の大きさのプログラムでも、単なるトークンの羅列でも構いません。

demo (`lib/rouge/demos/j`) は、[rouge.jneen.net](http://rouge.jneen.net/) に表示される短いコードです。

## テスト

README に書いてありますが、spec は rake を使ってテストします。visual sample は、rackup を実行してチェックします。(`localhost:9292` で demo が、`localhost:9292/j` で visual sample が表示されます。)

# こだわった点

※ ここに書いてある内容は、J 言語を知らない人には全く通じないと思われます。

J のコードをハイライトする上で、一番大切なのは、**全ての記号を演算子として扱ってはいけない**という点です。記号を色分けできなければ、シンタックスハイライトの意味が半減してしまいます。

そこで、verb を関数 (`Name.Function`) 、adverb/conjunction を演算子 (`Operator`) として扱うことにしました。これによって、`>:@i.` のような式が読みやすくなります。

可読性のための工夫点はもう一つあります。explicit definition の定義部分が文字列リテラルの場合 (例: `dyad : 'x + y'`)、リテラルの内部を式としてハイライトするようにしました。

# おわりに

実は Ruby をまともに書いたのは、これが初めてなのですが、案外簡単に書けたように思います [^3]。視覚的にデバッグできるのが楽でした。

Rouge にプルリクエストを送ったところ、無事マージされました :tada: 。先日リリースされた v3.24.0 に含まれています ([デモ](http://rouge.jneen.net/v3.24.0/j/))。

あとは Qiita が対応してくれるのを待つばかりです。 :qiitan: 

## 余談

Rouge v3.24.0 リリースのコメントより抜粋:

> This release has two new lexers: one for e-mails *(中略)* and one for J (why not another language starting with J?).

~~やっぱり J ってネタ言語扱いなんでしょうか……~~

[^1]: 実際には Minitest が使われていますが、基本的な書き方は共通しています。
[^2]: 当たり前ですが、コピペはダメです。
[^3]: Ruby というよりも、Ruby 上の EDSL を書いただけのような気がしますが。