antlr4を使う(2)

シーマークすずきです。

前回の続きで、antlr4が生成したパーサーソースをjavaから呼び出して、利用してみます。

■ eclipseのGradleプロジェクト作成

build.gradleは以下としました。antlr4が生成するソースをjp.co.seamark.csv.generatedパッケージに吐かれる様にします。また、構文木を捜索する方式は、リスナー方式とビジター方式がありますが、今回は、リスナー方式としました。

 

■ CSVの単純なパース

前回生成したソースのCSVBaseListenerクラスを拡張します。簡単にcsvファイルの全行をString型で受け取る方式とします。まずは、構文木の文字列形式に変換して返すだけにします。

実行すると、以下の様に前回grunでコンソール出力した結果と同じ結果が表示されました。(注意)fileToString()は割愛します。

テスト用のtest.csvは前回と同じ以下。

CSVSimpleParserの実行結果。

>AST=>(file (hdr (row (field user.name) , (field string#!user.age) , (field *omit*user.gender) \n)) (row (field suzuki) , (field 50) , (field male) \n) (row (field ange) , (field 12) , (field female) \n) (row (field seamark) , (field 20) , (field male) \n))

■ 構文木をウォークスルーしてみましょう

ParseTreeクラスをParseTreeWalkerクラスでwalk()することで、構文木の各構文ノードへのenter,とexitのリスナーイベントが呼ばれますのでメソッドをOverrideすることで自分用に処理を追加できます。

プログラムで処理する場合、構文ルール毎にexitほげほげのタイミングが分かりやすいでしょう。以前のCSV.gの文法を記述した際、構成要素はfieldというルールで各行rowが構成される、としました。fieldの構文ノードを抜けるタイミング、つまり、exitField()の関数でrowリストにfield文字列を追加していきます。先頭行のヘッダー行として、headerに、取り込みたい場合、exitHdr()のタイミングです。各CSV行の行毎のタイミングであれば、exitRow()となります。headerが空でない場合にCSVデータをheaderのラベルと一緒にMapに取り込みましょう。

■ JSON形式に変換してみる。

List<Map<String,String>> rowsに全ての行が取り込まれましたので、exitFile()をOverrideしてJSON形式に出力してみましょう。以下のメソッドを追記します。

出力結果は、以下。

もう少し考慮すれば、以前のNode.jsのcsvtojson.jsと同じ様な変換ができそうです。”string#!”や”number#!”がヘッダーに付いていれば、JSON型を文字列、数値型にするとか、”*omit*”が付いていれば、JSON出力しない、とか。残りは、後日。

antlr4を使う(1)

シーマークすずきです。

antlr4について少しメモをまとめておく。

  • antlr4とは

ANTLR (ANother Tool for Language Recognition)は強力なコンパイラコンパイラです。コンパイラとは、簡単に言えば、人が理解しやすい表現の記述から、目的に合わせてコンピュータが処理しやすい形式に変換することです。コンパイラは通常次のステップを行います。まず初めに、「元になるソースから構造を発見する」次に「目的に合わせて変換」します。後者の目的部分は、各コンパイラプログラムによって様々ですが、初めのステップは、一般的には更に次の処理に分割されます。

  1. ソースからトークン(token)に分割・分解し、トークンを発掘する(字句解析)
  2. 複数のトークンを構文に従って木構造に構造化(組み上げ)する(構文解析)

コンパイラコンパイラは、上記の字句解析と構文解析するコンパイラプログラム(別名パーサーと呼ばれています)を作成するためのコンパイラです。ANTLRを使い、構文定義(grammarファイル)を記述し、その定義に従って、つまり、grammarファイルをソースにして字句解析し構文解析するためのプログラムソースを生成してくれます。開発者は目的に応じて木構造を捜索したり、変換したりすれば良いのです。ANTLRをパーサージェネレーターと呼ぶ場合もあるようです。

  • 何ができる?

たとえば、JavaやC++のソースをパースするプログラムを作成することも可能です。他にも、日本語で「式」を定義し、それを計算するというプログラムも作成できます。”(上底 + 下底) * 高さ / 2.0”などの式の文字列をパースして、意味のある単位の「変数」や「演算子」や「括弧」のトークンに分割し、定義した優先順位でトークンを組上げて、それを順番に変数を数値変換して計算を行うための計算機プログラムを作成することもできます。

  • 使ってみる

パーサーは文法が定義できればほぼ何でも作成できます。HTML、JSONなどのデータファイルをパースするプログラムも面白いですが、ここでは、簡単なcsvのパーサーを作成してみましょう。まず初めに文法定義をします。以下に簡単なcsvのantlr4の文法定義です。CSV.gとファイル名を付けます。

CSVファイルの構造の定義をしています。CSVファイルは、ヘッダー(hdr)と複数の行(row)でできている、としています。複数個(0個以上)の行である表現は’+’が付いているからです。次に、ヘッダー(hdr)は1行(row)で構成されると定義しています。行(row)の定義です。行はフィールド(fieldがカンマ(,)で区切られて複数あること、行の終端は’\r”\n’或は’\n’である、と定義しています。’*’は0回以上の繰り返しです。定義はまだ、続きます。フィールド(field)はTEXTかSTRINGのいずれかで構成されると宣言しています。TEXTは、カンマ(,)か(’\n’)か(‘\r’)の何れでもない文字の0個以上の繰り返しです、としています。つまりTEXTは数値や英数文字も全てです。STRINGは、ダブルクオートで囲われていて、連続する2つのダブルクオートか1つのダブルクオート以外の全ての文字です。

文法定義ファイルをantlr4コマンドに以下のようにわたします。

> antlr4 CSV.g

> ls

CSV.g CSV.tokens CSVBaseListener.java
CSVLexer.java CSVLexer.tokens CSVListener.java CSVParser.java

生成されたJavaソースをjavacでコンパイルします。早速、antlr4の検証ツールgrunで試してみましょう。

> grun CSV file -tree

user.name, user.age, *omit*user.gender
suzuki,50,male
ange,12,female
seamark,20,male

Control+Dで、以下がコンソールに出力されます。

(file (hdr (row (field user.name) , (field user.age) , (field *omit*user.gender) \n)) (row (field suzuki) , (field 50) , (field male) \n) (row (field ange) , (field 12) , (field female) \n) (row (field seamark) , (field 20) , (field male) \n))

階層をGUI表示させたい場合は、-treeを-guiとします。

> grun CSV file -gui

user.name, user.age, *omit*user.gender
suzuki,50,male
ange,12,female
seamark,20,male

Control+Dで以下の画面表示がされます。

antlr4_parse_tree_1

  • 今後

次回は、grunを使わずに、javaプログラムからantlr4で生成したソースを呼び出して利用してみます。

 

csvからjsonへ変換する(メモ1)

シーマークすずきです。

開発プロジェクトでいつも取り組むことのひとつが、プログラムのテストのために、テストデータを用意することです。ここでは、先日使ってみたcsvtojsonというライブラリがよく出来ていたので、簡単に紹介します。

■テストデータの元ネタはCSVで作っとく

テストデータをプログラマーが書くことは良くありますよね。入力データと結果期待値データ。プログラムに喰わせる訳ですから、プログラムが読み易いフォーマットが良いのですが、お客さんにレビューしてもらう都合上、お客さんには表計算ソフトの書式で出したいことが良くあります。そこでよく利用されるのはcsv書式です。

■csvtojsonが便利

先日も参加している開発チーム内で、テストプログラムを開発している担当者側はJSONで欲しい。テスト仕様書作成担当者は、表計算ソフトで作成中です、っと。つまり、どっかで誰かが、csvからjsonに変換する必要が発生しました。csvは構造を持たないフラット構造ですが、jsonは構造を持つための階層構造。ネットで少し調べていたら、 node.jsのライブラリでcsvtojsonがよく出来ていそうだったので、使ってみました。

参考URL: https://github.com/Keyang/node-csvtojson

■インストール

上記URLからgit cloneして利用できます。または、 npmでインストール可能です。

>npm install -g csvtojson

■動かしてみる

csvファイルのサンプルは以下のsample.csvです。

$ cat sample.csv
user.name, user.age, user.gender
suzuki,50,male
ange,12,female
seamark,20,male

変換してみましょう。

$ bin/csvtojson < sample.csv
[
{“user”:{“name”:”suzuki”,”age”:50,”gender”:”male”}}
,
{“user”:{“name”:”ange”,”age”:12,”gender”:”female”}}
,
{“user”:{“name”:”seamark”,”age”:20,”gender”:”male”}}

]

とっても簡単です。出力したくない列には、*omit* を指定すれば、jsonに変換しません。

$ cat sample.csv
user.name, user.age, *omit*user.gender
suzuki,50,male
ange,12,female
seamark,20,male

*omit*指定したuser.gender列は以下の通り変換されません。

$ bin/csvtojson < sample.csv
[
{“user”:{“name”:”suzuki”,”age”:50}}
,
{“user”:{“name”:”ange”,”age”:12}}
,
{“user”:{“name”:”seamark”,”age”:20}}

]

これのおかげで、テスト仕様書をレビュー後、直ぐにjson変換ができました。