goyaccでjson5 likeなシンタックスのパーサとjsonのジェネレータを書いた

こんにちは、齋藤です。

今回の記事は golangアドベントカレンダー 2017の2日目の記事です。 フライングしています。

今年の golang の盛り上がり様はすごいですね。 Qiita に存在する golangのアドベントカレンダーは 4つもあります!

また、この記事を書いている時点で、AWS Lambda に golang のサポートが追加されたという速報が 弊社記事に上がっており、golang の人気はうなぎのぼりなのではないでしょうか! go go golang ! !

ちなみに現在上記リンクにある記事は 弊社の諏訪さんによって リアルタイム更新が行われておりました。強い。

golang アドベントカレンダー 1日目の記事はこんな感じ(投稿時点)でした。

2日目の記事は こんな感じ(投稿時点)だそうです!

  • その1 から niconegoto
  • その2 から tenntenn さんによる 「golang.org/x/tools/go/ast/astutilに入ったApplyでASTを書き換える話」
  • その3 から _EnumHack さんによる CBTという自作ツールの話
  • その4 から kami_zh さんによる 「最近GoでLinuxプログラミングをしているのでそれについてなど」

3日目の記事は

下の方々による記事が公開される様です!

  • その1 から niconegoto さん
  • その2 から gougyan さん
  • その3 から 74th さんによる cgoでポインタ渡しの話
  • その4 から ifsec_56 さん

はじめに

ブログに下書きを入れてたと思ったらなくてまじか、書き直しか。。。 と思ったけど大丈夫でした。ローカルのリポジトリに置いてました。

そんなこんなで今回は、goyacc を使って JSON5 like なパーサを書き、それを使った JSON ジェネレータを作りました。 名前は gojg です。 golang json generator なので安直に名前を決めました。

golang は初めて書くので、変なところがあったら コメントや PR で教えていただければと思います!

インストールは go get でどうぞ。 git と go が入ってたら動くはずです。

go get github.com/wreulicke/gojg

今回作成したツールのリポジトリはこちらです。

ざっと概要

gojg は JSON ライクなテンプレートから JSON を生成するツールです。 JSON を pretty-print する機能は現状実装していません。 現状では以下の機能を持っています。

  • テンプレートファイルの読み込み
    • 標準入力から
    • ファイルから
  • 生成したJSONの出力
    • 標準出力
    • ファイル
  • テンプレートパラメータの入力
    • CLIパラメータから
    • 別のJSONファイルから
  • JSON5 ライクなフォーマット
    • クォートなしのオブジェクトキー
    • trailling comma
    • 複数行文字列・シングルクォートによる文字列

実装した機能の紹介をしていきます。

テンプレートファイルの読み込み

gojg ではJSONライクなテンプレートファイルを読み込むことが可能です。 読み込み元としては 標準入力とファイル入力の2種類をサポートしています。

以下は パイプでテンプレートファイルを読み込む例です。

$ cat sample.template | gojg
# >> {"hogehoge":"test","test":"gwhoge\r\n\u2028","fsadosad":"hogehoge\\𩸽\n  hge"}

以下は ファイルからテンプレートファイルを読み込む例です。

$ gojg sample.template
# >> {"hogehoge":"test","test":"gwhoge\r\n\u2028","fsadosad":"hogehoge\\𩸽\n  hge"}

生成したJSONの出力

gojg ではテンプレートを基に生成したJSONの出力が可能です。 現状、出力先としては 標準出力とファイル出力の2種類をサポートしています。

以下は 標準出力に書き込む例です。

$ gojg sample.template
# >> {"hogehoge":"test","test":"gwhoge\r\n\u2028","fsadosad":"hogehoge\\𩸽\n  hge"}

以下は ファイルに書き込む例です。

$ gojg sample.template --output sample.json

--output-oで省略することも可能です。

テンプレートパラメータの入力

gojg ではジェネレータというだけあって、テンプレートのパラメータを外部から読み込みます。

以下は 別のJSONファイルからテンプレートのパラメータを解決する例です。

$ gojg sample.template --context-file parameter.json

--context-file-f で省略することが可能です。

以下は CLIパラメータから解決する例です。

$ gojg sample.template --context test=2

--context-c で省略することが可能です。

また、以下のように複数指定することも可能です。

$ gojg sample.template -c foo=1 -c bar=2 

テンプレートの組み合わせによってはJSONとしては、不正な出力が吐き出されてしまうかもしれません。 この辺のバリデーションもしたいなぁとかぼんやり思っています。

JSON5 ライクなフォーマット

テンプレートファイルでは 普通のJSONとは違い、JSON5のようなフォーマットで記述することが可能です。 現状は以下のJSON5 inspiredな機能が存在します。

  • クォートなしのオブジェクトキー
  • trailling comma
  • 複数行文字列・シングルクォートによる文字列

以下の例はクォートなしでオブジェクトのキーを記述している例です。

$ gojg << EOF
>> {
>>   hoge: "test"
>> }
EOF
# >> {"hoge":"test"}

以下の例は オブジェクトのキーの最後にカンマを書くことができる例です。

$ gojg << EOF
>> {
>>   hoge: "test",
>> }
EOF
# >> {"hoge":"test"}

以下の例は 複数行文字列やシングルクォートでの文字列の記述をしている例です。

$ cat ex.template
# >> {
# >>   hoge: 'foo" bar',
# >>   fuga: `Lorem Ipsum is simply dummy text of the printing and typesetting industry.
# >>          Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
# >>          It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
# >>          It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`,
# >> }
$ gojg ex.template
# >> {"hoge":"foo\" bar","fuga":"Lorem Ipsum is simply dummy text of the printing and typesetting industry.\n         Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.\n         It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.\n         It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."}

感想とまとめ

今回なぜこのツールを作ったかというと 存じてないだけなのかもしれないですが、JSONのスタブを作るCLIツールが欲しいと思い ちょうどいい機会なので、golangの勉強しようと思い、このツールを作りました。 ただ結局使ってなかったりしますので、バグはまだまだ残っていそうです。

golangは初めてだったので、まずはJSでpegjsを使って プロトタイプを実装しました。 現在の実装とは変わっているのですが、pegjsで書いた時はこんな感じの文法規則でした。

その後、紆余曲折あり、golangで実装を始めました。

最初に作ったのは goyacc で調べると出てきた記事で書かれていた text/scanner パッケージを使って Lexerを書いていました。これは、goyaccの動きをいまいち分かっていなかったという点と Lexerの書き方が分かっていなかったところが大きいです。 そんな感じで Lexer と Parser を同時並行しながらテストを書いてました。

mattn さんの anko や Linda_pp さんの gocaml を見ながら、Lexer を自分で書き始めました。 lexerを自分で書いたことがなかったので、めちゃくちゃしんどかったのですが、なんとか自前のLexerに移行ができました。 参考にしたリポジトリ・記事は非常に助かりました。本当に感謝したいです。

そんなこんなで JSON の出力部分と CLI のインターフェースを作成してたら楽しくなって、 JSON5 ライクに、 JSON のキーをダブルクォートで囲まなくても書けるようにしたり、 複数行文字列を書けるようにしたりしました。

制御文字を JSON に出力する時のエスケープをしていなかったりしますし golang のお作法に合っていない部分が存在すると思うので、もう少しブラッシュアップしていきたいですね。

今回簡単なツールを書いてみて思いましたが 非常に golang は使いやすい言語だなぁと思いました。 Web アプリも golang で書いてみたいですね。

一つ思うのはテンプレートの規則がそれなりにイケてない感があるのが 心残りとしてあるので、改良していけたらな、と思っています。

最後に。記事中に golang 本体の話がないことに気づきました。

参考

参考にしたサイト、リポジトリです。本当にありがとうございました!