TECH-MICCHON.jar

Scalaを中心に技術的な話題を書きます。

2017年を総括する

id:biacco42 さんの記事

biacco42.hatenablog.com

に影響を受けたので,自分もまとめていきたい.

TL;DR

  • 我慢の年
  • 研究が思いの外しんどかった
  • 来年は復活の年にしたい

やったこと

1月

このときに期末試験と研究室配属があったので,色々と気が気じゃない毎日を過ごしていたのは覚えている. あと,ドワンゴでのインターンは継続してやっていた.

期末試験の出来はまずまずで,しっかり単位を揃えることが出来,来学期もきっと上手くだろうと感じた.

研究室配属はもう一度やり直せるならやり直したいが,今の研究室も悪くはないので正直なんとも言えない.

2月

研究室に配属され,輪講をやり始めた.

Types and Programming Languages という本を22章まで,必要な章だけピックアップしてゼミ形式で読み進めていった.

インターン輪講の準備を並行で行うのは結構大変で,退勤したら会社のリフレッシュルームで明日の予習をする生活だった.

3月

3月末尾付けでドワンゴでのインターンを終了した.

micchon.hatenablog.com

ドワンゴでは楽しい思い出がたくさんあり,離れるのが正直寂しかったが, 良い区切りだと思ったので思い切って退職.

4月

新学期.卒業に必要な単位はあと2つだったので1個だけ履修登録し,あとはワクワク研究室ライフを過ごす.

研究テーマをどうしようかは非常に悩んだ.

自分は Scala というプログラミング言語が本当に好きなので, Scala に関係することをやりたかった.

ただ,指導教員と色々と話をしていくうちに,それは卒論ではテーマ的に取り組めないと言われ, PyPy の Tracing JIT に関することをやる羽目に.これが大きな分岐点だったかもしれない.

5月

FOLIO という証券スタートアップにエンジニアインターンとして入社した.

corp.folio-sec.com

週1日でも出来る仕事を探していて,ドワンゴ時代の知り合いがたまたまそこにいたこともあり,お互いの条件がマッチしたことで入社に至る.

このときは研究室オンリーの生活に息苦しさを感じており,外界の刺激を欲していたのだと思う.

6月

FOLIO でのインターンではまずバックエンド開発の環境改善に取り組んだ.これは思いの外楽しくて,FOLIOでの仕事にのめり込むきっかけにもなった.

研究も同時並行で進めており,院試がちらついて来たので段々余裕がなくなってきた.本当にこの時期は胃が痛かった.

7月

院試へ全力を尽くす.

インターンはお休みし,その間はずっと院試の過去問を解きまくった.あまりの不安から10年分を解いてしまったが,正直ここまですることは無かったと思う.学部時代の復習と過去問5年分をしっかりやっていれば受かる.

8月

院試本番.

プレッシャーからか体調を崩す.

8月上旬は自宅の机にむかうだけでしんどかったので,なるべく気を紛らわそうと近所のロイヤルホストで勉強していた.

しかし,ロイヤルホストではなんとかペンを走らせることは出来るものの,家では全く勉強できなかったので,代わりにずっと映画を見ていた.

オススメの映画を教えてもらって,その中から気になるのを見ていった.見たのはざっと挙げると

です.

9月

院試に合格する.

見たところ内部生はほぼ全員合格していた.

だが,ここで胸をなでおろすのはまだ早い,私達は休む間もなく卒業研究に取り組まねばならなかった.

自分は Meta-tracing JIT という技術の改良に挑むことになる.これを成し遂げればどの学会に持っていっても通用するであろうと,うちの指導教員は自信をもって勧めてくれた.

10月 - 11月

研究に没頭していた.

唯一楽しかったのは10月の工大祭でアイカツスターズ!の声優さんのトークショーに参加したことくらいで,この時はひたすらプロジェクトを進めていた.

Meta-tracing JIT の "Meta" って何だよって思いながら脳を振り絞って論文に立ち向かっていた.同時に幾つかサーベイもしたが,参考になりそうな先行研究がなかったので不安を感じる.

12月

焦り.

11月までに Tracing JIT を作り終えていたかったが,まだ終わらない.一体どうなってしまうんだ?という不安が日に日に増していく.

クリスマスの日のミーティングでレールを敷いてもらいかなり楽になったが,それまでは先の見えない森の中を一人で突き進んでいるような感覚だった.

まとめ

2016年は自分にとって良いことがたくさんあり,インターンに行ったり友達も増えたりでポジティブな年だった. 一方で,2017年は2016年の揺り戻しが来たかのような年だったといえる.けれども,自分としては,耐えることの多い中で何気ない幸せを見つけるような,繊細な感情を養うことが出来たかもしれない.これから生きていく上で必要な経験だったと割り切って,次に進んでいくしかない.

2018年は大きな変化が起こる年だと言える.今思い浮かぶだけでも

  • 卒論
  • 卒業式(大学)
  • 入学式(大学院)
  • インターン
  • 就職活動

といったイベントがあり,きっとこれ以外にも小さい出来事が何度も切れ目なく自分に訪れるかもしれない. だからこそ,来年は今年の経験を踏まえ,残り少ない学生生活を有意義なものにしていきたい.

Tracing the Meta-levelとは?PyPyのJITコンパイラについて

本記事はFOLIO Advend Calendar 22日目のものです. 昨日は yoshiko_pg さんで「git grepでソースコード内検索のあれこれ」でした.

はじめに

みなさんは PyPy についてご存知ですか? PyPy とは非常に高速な Python 処理系で,通常の CPython に比べると約8倍の高速化を達成しています1.

何故早くなったのか,それは,Meta-Tracing JIT コンパイラを搭載した特殊な言語によって実装され,PyPy には JIT コンパイラが付属しているからなのです.

以降では JIT の話は省略しますが,一言でいうと「よく実行される部分を機械語コンパイルして実行する」というもので,プログラムの大幅な速度向上が期待できます.

今回は,その特殊な言語 RPython と Meta-Tracing JIT という技術について触れていこうと思います.

RPython について

RPython とは Python のサブセットです.元は PyPy を実装するために作られた言語でしたが,最近では独立して配布されるようになりました2. 処理系として面白いのは,keen さんのブログ3にもありましたが,Python コードをC言語Java バイトコードなどに変換するコンパイラでもあるのです.

さらに,この RPython は Meta-Tracing JIT コンパイラという技術によって任意の JIT コンパイラ付きの処理系を実装することが可能になりました.

Tracing JIT コンパイラ

Meta-Tracing JIT の説明する前に RPython の Tracing JIT について軽く触れておきます.

下のプログラムは,0から9999までの和を計算する単純な Python プログラムです. 左にあるのは meta-program-conter というもので,命令の並んでいる順番とだけ覚えておいてください.

0   sum = 0
1   i = 0
2   while i < 10000  # conter += 1 implicitly
3      sum += i
4      i += 1

この meta-pc が「再び同じ値になる」という状態に何度も繰り返しなったとします.そして,ある閾値を超えると RPython はその部分をループだとみなすのです.

具体例を出すと分かりやすいでしょう.まず,このプログラムを上から順番に実行するとして,meta-pc がどんな値になるか手で書き下してみると,

0, 1, 2, 3, 4, 2, 3, 4, 2, 3, 4 ...

となりますね.2, 3, 4 の塊が何度も現れているのが分かると思います.

2 のところに来たら counter が 1 だけ RPython のレベルでインクリメントされます.

そして,ある閾値を超えるとRPython はここをループだとみなし,その部分を「トレース」として JIT コンパイルします.

以上が Tracing JIT の大雑把な説明になります.

Meta-Tracing JIT コンパイラ

Meta-Tracing JIT コンパイラの話に移りましょう.ところで, Meta-Tracing JIT は PyPy の中ではどういった立ち位置にあるのでしょうか.

まず,以下の関係にあることを念頭に置きましょう.

実装する言語 実装される言語
RPython Python

RPython で Python を実装するということは,すなわち RPython 言語で Python インタプリタを実装するということです.

したがって, RPython JIT コンパイラは RPython で書いた Python インタプリタをトレースしなければなりません.

インタプリタをトレースする

インタプリタをトレースするというのは一体どういうことでしょうか.まず RPython の Meta-Tracing JIT の中核部分を見てみます. 下のコードは,とあるインタプリタの大枠だと思ってください.

def interp(regs): 
    pc = 0
    instr = bytecodes[pc] 
    while True: 
        jit_merge_point(pc=pc, ...) 
        if instr == ADD:
           ... 
        elif instr == IS_GT: 
           ... 
        elif instr == JUMP: 
           ... 
           can_enter_jit(pc=pc, ...)
          ...

しかし,このコードをただ Tracing JIT しただけでは,プログラムのループを正しく判定できません.何故なら「インタプリタのループとプログラムのループというものは同じループだけど意味が違う」からなのです.

インタプリタループとプログラムループ

インタプリタループとは,各オペコードに対して適切な処理へと ディスパッチし続けている while ループ のことを指します.

一方で,プログラムループとは, ユーザーが書いたプログラムにあるループ のことです.

Meta-tracing JIT が本当に JIT したいのは プログラムループ であることは間違いないでしょう.

この2つは意味は違うといっても「ループする」という性質は同じなので,そこを上手く区別してやらなければなりません.

RPython では

  • jit_merge_point
  • can_enter_jit

の2つの API が2つのループを区別する上で重要な役割を担います.

jit_merge_pointJIT コンパイラに現在の pc の状態を教えるためのメソッドです.ディスパッチループの先頭に置き,{meta-pc, pc}をセットで再び同じ状態になるかどうかを監視します.

can_enter_jitJUMP 命令などの pc が若い値になる命令を解釈する部分に置きます.それによって,JIT コンパイラに Tracing を開始させます.

以上のことをすると,Meta-Tracing JIT コンパイラは実行するプログラムのループを検知し,そこを Tracing JIT します.これが Meta-Tracing JIT の仕組みです.

PyPy の構造

Python VM は RPython によって実装され,その RPython には tracing-JIT コンパイラが付属しています.

ただ, PyPy を利用する側からは RPython という言語の存在は分かりません.

それはあたかも,RPython の JIT コンパイラPython VM のメタレベルを tracing-JIT したかのようです.これが Meta-Tracing JIT の真髄なのです.

image.png

余談

RPython で実装された処理系として, 有名なものだと

でしょうか.古い記事ですが http://rokujyouhitoma.hatenablog.com/entry/2015/06/12/115239 によくまとまっています.

Meta-Tracing JIT の話は Tracing the Meta-Level: PyPy’s Tracing JIT Compiler にあるので,興味のある方は一読してみるといいでしょう.

ちなみに,自分の卒業研究では OCamlMinCaml の Meta-Tracing JIT コンパイラを作っています.ココロが何度折れたか分からないほどしんどいですが,なんとか動くところまで実装して卒業したいです!

OSS のメンテナになるということ

皆さんはじめまして。 FOLIO でエンジニアとしてインターンしているみっちょんです。普段は大学で Tracing JIT Compiler の研究をしています。

本記事はFOLIO Advend Calendar 10日目のものです。 昨日は matsu_chara さんで「Application Crash Consistency and Performance with CCFSを読んだ」でした。

scalaenv という OSS のメンテナになってからやってきたこと、そしてやるべきこと

scalaenv とは

scalaenv とは、Scalaのバージョンマネージャです。

github.com

Ruby には rbenv があり Python には pyenv があるように Scala にも同様のツールがあります。

自分がこのプロダクトにいくつか Pull Request を送っていたのと、作者さまと Twitter でつながっていたという縁もあり、2017年の夏くらいにメンテナになりました。

以降では、 scalaenv のメンテナになってからやってきたこと、そしてメンテナとしてやるべきことを書いていこうと思います。

OSSメンテナとは、命をつなぐ者である

メンテナとはOSSの維持・管理を行っていく人間のことで、言ってしまえば OSS の生命維持装置に他なりません。

メンテナがいなければそのプロダクトは緩やかな死を迎えていきます。

人や組織からお金をもらってメンテナになる場合もありますが、世の中にある殆どのOSSは有志によって維持・管理がなされています。 そして、メンテナはOSSに対して責任を持ち、開発・運用していきます。

メンテナとしてやってきたこと、そしてやるべきこと

OSSのメンテナとして最低限すべきであると思った作業は以下の三つです。

  • issue の整理
  • Pull Request のレビュー
  • 定期的なリリース

issueの整理

issue は往々にして溜まっていくものですが、それを放置しておくのはプロダクトにとっての停滞を意味します。

したがって、 OSS メンテナは最初にどんな issue が寄せられているのかチェックし、可能であればその場で対応していくのが望ましいです。

例えば、 scalaenv には

のような重要な要望が寄せられていました。そういった issue を分類し、優先順位を付け、可能であればマイルストーンを付けて対応していくのがメンテナの役割です。

幸い、当時はまだ時間が余っていたので、いずれの issue も解決し closed な状態にできました(めでたい)。

ただ、自分が立てた issue でまだ対応出来ていないものがあるので、それを解消していくのがこれからの目標です。

Pull Request のレビュー

Pull Request が放置されているのは OSS の利用者にとって好ましい状況ではありません。

したがって、 OSS のメンテナになったらPull Request のレビューも issue の整理と同様に重要な作業です。

scalaenv のメンテナになってからはほぼ自分が修正点をコミットしていったので、まだ一つしか Pull Request を受け取っていませんが、溜めている Pull Request は一つもないという状態を維持しています。

定期的なリリース

OSS の利用者にとって、定期的なリリースが行われ、常に改善がなされていることは良いことだと考えます。

なぜなら、アクティブなコミッタがその OSS にいることを意味しますし、仮に自分がバグを見つけたり機能追加のアイディアを思いついた場合、コミッタに報告すればすぐ対応してくれるだろうという見込みが立てやすくなるからです。

アクティブにコミットする

つまり、 OSS メンテナはある程度アクティブに開発しているというアピールをしなければなりません。

私がメンテナになった時、開発はやや停滞気味であったので、まずは目先の問題を解消していこうと考えました。その結果、

  • 機能追加 / バグ修正
  • 定期的なリリース

を適宜行っていけば利用者はきっと関心をもってくれるだろうというところに落ち着き、 OSS 利用者にとって意味のあるバージョンを提供していくことを念頭に置いて開発に取り組みました。

その結果

自分がメインのコミッタになったのは 0.0.13 以降ですが、リリースノート を見ると、大きな変更があったらその都度リリースを行っていったので、健全なサイクルになっていったと感じます。

homebrew にもリリースのための Pull Request をしたり、

github.com

プロダクトの命をつなぐためにやれるべきことをしていきました。

まとめ

OSS のメンテナンスは地道な作業の連続です。しかし、 OSS は誰かが面倒を見ないとそのまま放置され、誰にも看取られずプロダクトとしての死を迎えてしまいます。

だからこそ、 OSS のメンテナンスは重要であり、その OSS に利用者がいる限り責任をもって開発に取り組むべきであると思いました。

株式会社ドワンゴでのインターンを終了しました

2017年3月30日付けで株式会社ドワンゴを退職しました。自分はインターンアルバイトとして入社したので、正確にはインターンを終了したということになるのでしょうか。

最初は右も左もわからない状態でしたが、メンターさんたちが根気強く支えてくれたお陰でここまで長い期間取り組めたのだと思います。この場を借りて改めてお礼申し上げます。

さて、せっかくなのでドワンゴでの思い出や取り組んできたことについて綴っていこうと思います。

思い出

2016年8月の入社から8ヶ月、主に Scala と playframework で課金系のシステムを作るお仕事をしていました。最後は新しいプロジェクトの立ち上げにも関わったりしました。

また、Scala 本体や akka とかにコミットしている方が同じフロアにいて席も年齢も近かったので仲良くなれたりとか、 scalaz コミッターのあの方がいらっしゃってランチで交流できたりするのがまた面白いところでした。

労働時間

ドワンゴはフレックス制です。私のチームは完全フレックスだったので、急なスケジュール変更にも対応できすごく助かりました。

大学の講義をこなしながらだったのでこれはとてもありがたかったです。

美容室と整体

ドワンゴには美容室と整体があります。

僕は美容室を何回か利用しました。インターン生でも使えるのでありがたかったのと、勤務時間中に行けるのでコーディングしながら髪を切ってもらえました。

また、表参道の一流の美容師が来てくれるので、結構いい感じに切ってもらえます。

ランチ

銀座はランチがとても美味しいです。色々なところに行きましたが、次のお店がおすすめです。

  • 牛庵
    • ここは最高級のお肉が1000円で食べれるから本当にすごい。最高。

取り組んだこと

入社して最初の頃は管理ツール用の API を作っていました。DDD の D の字も分からなかったのでコードを追うのにとても苦労しましたが、DDD の青い本を買って読んだり、先輩に色々聞いたりしてなんとか身につけました。

あと、インターン生である自分にもレビュアーをやらせていただいたのはとてもいい経験でした。今まで人に見てもらうことしかできなかったので、やってみて人のコードを採点するというのは結構勉強になるなぁと感じました。

その後は、バックログからタスクをもらってこなし、最後はプロジェクトの立ち上げにも関われました。特に、一からバックエンド部分の設計をやれたのはとてもいい経験になりました。

OSS

ドワンゴでは勤務時間中にもある程度は OSS 活動をしてもよいということになっています。なので、業務中に気づいたことがあったら、色々なプロダクトに PR を送ったりしてました。 特に playframework に PR 送ってマージされたときがハイライトでした。

github.com

他には、scalafx という javafxScala ラッパーライブラリの sbt をアップデートしたり、breeze という数値計算ライブラリの build.sbt を整備したりしました。

あとは自分で sbt プラグインを書いたりと、色々やってきたかなーと思います。

github.com

最後に

まだまだやっていきたい気持ちはあるのですが、ラボ生活がはじまるとスケジュールを合わせるのがちょっとむずかしくなるということもあって、キリの良いところで終了となりました。

自分は大学院に進学する予定なので就活はまだ先ですが、そのときは関係者の皆様にまたお世話になるかと思います。よろしくお願いします。

ほしいものリスト

来月から無職(?)なので、よろしくお願いします。

http://amzn.asia/3ArPBpP

東工大ポータルにSeleniumでブラウザから自動ログイン

東工大ポータルにSeleniumで自動ログイン

自分の大学のポータルサイトには二段階認証が付いており、それは一時的にtokenを発行するようなものではなく、よくネットバンキングで使われるようなマトリックス認証である。

セキュリティ的には幾分強くなるのだが、ちょっと使い勝手は悪い(Google二段階認証を使わせてくれないだろうか…)。

そこで、ちょっと不便な現状を打破すべく自動ログインツールを作ろうと思った。

一方、「東工大ポータル 自動ログイン」でぐぐってみると結構な数の「ログインツールつくってみた」系の記事がヒットする。 じゃあこれでは二番煎じなのじゃ…と思ったけど、既存のツールはだいたい

の二種類しかなさそうというのが、ちょっと調べてみて分かった。

そこで今回作ったのはブラウザを操作して自動ログインするツールである。

github.com

追記

より安全な方法に実装を変更し、それにともなって使い方の章を修正しました。

使い方

拙い英語で README.md に記したのだが、大事なことなのでここでもまた説明する。

準備

以下のフローでこのスクリプトを動かす環境ができる。

  • firefox を用意する
  • $ make
  • $ make build.<your os>
    • your os というのは macox, linux64, win64 である。

ログイン(追記)

以前の方法ではリポジトリに直にパスワードを置くためセキュリティに問題があった。 ここで、pitというライブラリを使い、設定ファイルを外に置くことにした。 設定の方法は、make したあとに

$ vi ~/.pit/default.yaml
titech:
  usr_name: YOUR_USER_NAME
  usr_password: YOUR_PASSWORD
  usr_matrix: ['xxxxxxx', 'xxxxxxx', 'xxxxxxx', 'xxxxxxx', 'xxxxxxx', 'xxxxxxx', 'xxxxxxx', 'xxxxxxx', 'xxxxxxx', 'xxxxxxx']

default.yaml を編集すればOK。 usr_matrix はAからJまでの列にあるアルファベットを配列でflfejfewみたいに記入する。

ログインする

  • $ make login

これでOK。

実装

Selenium-webdriverを使って簡単なスクリプトRuby で書いてみた。簡単に説明していく。

@driver = Selenium::WebDriver.for :firefox

これでブラウザを操作するドライバーインスタンスを作っている。これがSeleniumスクレイピングするときのすべての始まり。

以下は、細々とした実装になってしまった。

@driver.get 'https://portal.nap.gsic.titech.ac.jp/GetAccess/Login?Template=userpass_key&AUTHMETHOD=UserPassword'
usr_name = @driver.find_element(:name, 'usr_name')
usr_name.send_keys(@usr_name)
usr_pass = @driver.find_element(:name, 'usr_password')
usr_pass.send_keys(@usr_password)
@driver.find_element(:name, 'OK').click

sleep 3

ここで Basic 認証を突破している。

@driver.find_element(:name, 'usr_name') でIDを入力するelementを見つけてきて、そこにusr_nameを入力。

passwordの部分も同じである。

sleep 3 は認証してレスポンスが返ってくるまで時間がかかってしまい、waitしないとエラーになるため入れいている。

def get_password_value_from_message(message)
  @usr_matrix[message[0]][message[1] - 1]
end

def get_message_value_from_xpath(element)
  [element.text[1], element.text[3].to_i]
end

xpath_list = [
  '/html/body/center[3]/form/table/tbody/tr/td/table/tbody/tr[5]/th[1]',
  '/html/body/center[3]/form/table/tbody/tr/td/table/tbody/tr[6]/th[1]',
  '/html/body/center[3]/form/table/tbody/tr/td/table/tbody/tr[7]/th[1]'
]

pass_list = [
  @driver.find_element(:name, 'message3'),
  @driver.find_element(:name, 'message4'),
  @driver.find_element(:name, 'message5')
]

message_value = xpath_list.map { |e|
  get_message_value_from_xpath(@driver.find_element(:xpath, e))
}

pass_list.zip(message_value).map { |pass, message|
  pass.send_keys(get_password_value_from_message(message))
}

@driver.find_element(:name, 'OK').click

これは、xpathマトリックス認証に必要な要素を見つけて来て、そこから必要なアルファベットを取得している。 この部分が一番実装していて面倒だった。

最後に

東工大ポータルのログインツールについて色々調べてみたので、あとでこれらについてまとめて記事にしてみても面白そうな気がした。

sbt plugin の作り方 - 実際の plugin を用いて解説 -

概要

sbt plugin の作り方をこちら

github.com

のコードを使いながらざっくり解説していきます。

sbt plugin を作る

sbt plugin はいくつかの Key に対してその振る舞いを記述していく、というスタイルで実装していきます。 振る舞いとは、例えば

  • Groovy など ScalaJava 以外のソースをコンパイルする
  • プロジェクトを jar に固めてデプロイする

などがあると思います。

sbt plugin はおおまかに分けて

  • Key
  • Task
  • projectSettings

という三つの要素があり、plugin 開発者はそれぞれを実装していく必要があります。

そのテンプレートは次のようになります。

import sbt._
import Keys._

object SimplePlugin extends AutoPlugin {

  object autoImport {
    /** implement your keys */
    ???
  }

  import autoImport._

  override def trigger: PluginTrigger = allRequirements

  override val projectSettings = ???

  lazy val yourTask = Def.task { 
    /** Implement your task(s) */
    ??? 
  }
}

Key

Key には以下の三種類があります。

  • settingKey: 属性、可変の値
  • taskKey: 実際の振る舞い
  • inputKey: taskKey でコマンドライン引数を受け取りたい場合の振る舞い

今回は settingKeytaskKey を使いました。

実際に定義したいのは次の4つです。

  • jflex ファイル *.flex の場所
  • 生成するファイル *.scala の場所
  • 実際に生成する振る舞い
  • その他設定

しがたって、以下のように実装しました。

 object autoImport {
    lazy val jflexSourceDirectory = settingKey[File]("jflex-source-directory")
    lazy val jflexOutputDirectory = settingKey[File]("jflex-output-directory")
    lazy val toolConfiguration = settingKey[JFlexToolConfiguration]("jflex-tool-configuration")
    lazy val pluginConfiguration = settingKey[PluginConfiguration]("jflex-plugin-configuration")
    lazy val jflexGenerateWithCompille = settingKey[Boolean]("jflex-with-compile")
    lazy val jflexSources = taskKey[Seq[File]]("jflex-sources")
    lazy val jflexGenerate = taskKey[Unit]("jflex-generate")
  }

Task

Key を定義したら次は Task を定義します。Task とは「実際の動作」であり、jflex-scala-plugin では

  • jflex API を叩いて *.flex ファイルを生成する

という振る舞いが想定されます。したがって、次のように実装しました。

lazy val jflexGeneratorTask: Def.Initialize[Task[Unit]] = Def.task {
    generateWithJFlex(
      jflexSources.value,
      jflexOutputDirectory.value,
      toolConfiguration.value,
      pluginConfiguration.value,
      streams.value.log
    )
  }

  private[this] def generateWithJFlex(
    srcDir: Seq[File],
    target: File,
    tool: JFlexToolConfiguration,
    options: PluginConfiguration,
    log: Logger
  ): Unit = {
    target.mkdirs()

    log.info(s"JFlex: Using JFlex version ${Main.version} to generate source files.")
    Options.dot = tool.dot
    Options.verbose = tool.verbose
    Options.dump = tool.dump
    Options.setDir(target.getPath)
    Options.emitScala = tool.emitScala

    val grammars = (srcDir ** ("*" + options.grammarSuffix)).get
    log.info(s"JFlex: Generating source files for ${grammars.size} grammars.")

    grammars.foreach { g =>
      Main.generate(g)
      log.info(s"JFlex: Grammar file ${g.getPath} detected.")
    }
  }

projectSettings

ここには、デフォルトの挙動(初期値)を記述していきます。

sbt-jflex-scala の場合、次のような初期値を設定しました。

  • *.flex ファイルはデフォルトで src/main/flex にする
  • 生成される *.scala ファイルはデフォルトで src/main/scala/flex にする
  • コンパイル時の自動生成はデフォルトではオフ

したがって、次のような実装になりました。

  override val projectSettings: Seq[Setting[_]] = Seq(
    jflexSourceDirectory := sourceDirectory.value / "main" / "flex",
    jflexOutputDirectory := sourceDirectory.value / "main" / "scala" / "flex",
    toolConfiguration := JFlexToolConfiguration(),
    pluginConfiguration := PluginConfiguration(),
    jflexSources := (jflexSourceDirectory.value ** "*.flex").get,
    jflexGenerate := jflexGeneratorTask.value,
    unmanagedSourceDirectories in Compile += jflexSourceDirectory.value,
    jflexGenerateWithCompile := false
  ) ++ Seq(
    compile := {
      if (jflexGenerateWithCompile.value)
        (compile in Compile).dependsOn(jflexGenerate).value
      else
        (compile in Compile).value
      }
  )

これで、sbt plugin のパーツが整ったので、あとはテンプレート部分を埋めるだけです。

全体像はこちらを参照してください。

参考

jflex-scala の sbt plugin を作った話

JFlex とは

JFlex とは Java で書かれた構文解析器を生成するためのライブラリで、Javaのコードを生成します。

sbt-jflex-scala を作った動機

JFlex の fork の中で jflex-scala というものがあり、これは Scala のコードを生成してくれます。

github.com

これを sbt 上で利用するための sbt プラグインはいくつかあるのですが、どれもあまりメンテされておらず、動作しないため、この際新しく作り直しました。

3tty0n/sbt-jflex-scalaホスティングしてあります。

github.com

使い方

使い方は単純で

  • project/plugins.sbt に次の記述を追加
 addSbtPlugin("com.github.3tty0n" % "sbt-jflex-scala" % "0.1.1")
  • *.flex ファイルを src/main/flex に置く
  • $ sbt jflexGenerate

Yylex.scala が生成されます。

オプションで

jflexWithComple := true

とすると、コンパイル時に Yylex.scala を自動生成してくれます。

最後に

イマイチ需要はわからないけど、構文解析する人にとってはちょっと便利になるかもしれません。

この次は sbt plugin の作り方をまとめてみたり、 Elm の話もできたら良いと思っています。