はじめに
最近 J 言語 にハマっているのですが、残念なことに Qiita のシンタックスハイライトは J をサポートしてくれていません 。J は特に見た目がアレな言語なので、 記事を書く側も読む側もシンタックスハイライトがあるとモチベーションが上がると思います。
ということで、自分で実装することにしました。その記録を、ここに残しておこうと思います。
他の言語のシンタックスハイライトを実装する時に参考になるかもしれませんし、J 言語が他の言語と違いすぎてあまり参考にならないかもしれません。
大まかな手順
Qiita のシンタックスハイライトには現在 Rouge という Ruby 製のライブラリが使われています。このライブラリにプルリクエストを送ることで、J のハイライトを追加しようと考えました。
Rouge をフォークしたら、lexer 開発のガイド を見ながら始めます。
最初に、lexer を定義するファイルと、spec を追加します。
# -*- 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
# -*- 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
が必要ですが、始めは空のファイルで構いません。
あとは、以下の手順を並行して行います。言葉で解説するより実際のコードを見た方が早いので、細かい説明は省きます。知っている/よく使う言語の lexer を見れば大体分かると思います。
spec を書く
RSpec の書き方を知っていれば、特に困ることはないと思います 1。
テストには assert_tokens_equal
を使います。
assert_tokens_equal "コード", トークン1, トークン2, ...
トークンは、[名前, テキスト]
の組で表します。トークンの名前については、一覧 を参照してください。
実際のところ 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 に表示される短いコードです。
テスト
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 にプルリクエストを送ったところ、無事マージされました 。先日リリースされた v3.24.0 に含まれています (デモ)。
あとは Qiita が対応してくれるのを待つばかりです。
余談
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 ってネタ言語扱いなんでしょうか……