パーサ生成フレームワーク
YACCとの違いは外部ファイルを必要としない点が違う。
Ruby版も存在しており、rparsecという。言語の先頭1文字をとって区別をつけている。 haskell版もあるがこちらが、元になっているので、こちらの名前はparsecという。
http://jparsec.codehaus.org/jparsec2+Tutorial
だいたいチュートリアルの和訳相当だと思っていいです。
http://d.hatena.ne.jp/taichitaichi/20071008/1191808121
実は、jparsecのソースコードをダウンロードすると、計算機のサンプルコードが入っていて、
これがチュートリアルで解説してあるようなコードよりもすっきりさわやかなコードなのだ。
だから、このサンプルから逆に構築する手順を、観察力+妄想力で、作り、俺的チュートリアルをつくるのだ!。それが、漢ってもんだろ。
はっきりとしたゴールがあるってことは、それだけでも、しあわせなことなのさ。
/** * The main calculator parser. * * @author Ben Yu */ public final class Calculator { /** Parsers {@code source} and evaluates to an {@link Integer}. */ public static int evaluate(String source) { return parser().parse(source); } static final Parser<Integer> NUMBER = Scanners.INTEGER.map(new Map<String, Integer>() { public Integer map(String text) { return Integer.valueOf(text); } }); static final Binary<Integer> PLUS = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a + b; } }; static final Binary<Integer> MINUS = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a - b; } }; static final Binary<Integer> MUL = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a * b; } }; static final Binary<Integer> DIV = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a / b; } }; static final Binary<Integer> MOD = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a % b; } }; static final Unary<Integer> NEG = new Unary<Integer>() { public Integer map(Integer i) { return -i; } }; private static <T> Parser<T> op(char ch, T value) { return isChar(ch).retn(value); } static Parser<Integer> parser() { Parser.Reference<Integer> ref = Parser.newReference(); Parser<Integer> term = ref.lazy().between(isChar('('), isChar(')')).or(NUMBER); Parser<Integer> parser = new OperatorTable<Integer>() .prefix(op('-', NEG), 100) .infixl(op('+', PLUS), 10) .infixl(op('-', MINUS), 10) .infixl(op('*', MUL), 20) .infixl(op('/', DIV), 20) .infixl(op('%', MOD), 20) .build(term); ref.set(parser); return parser; } }
jparsecをダウンロードし、解凍すると [jparsec-2.0_src]-[examples]-[src]-[org]-[codehaus]-[jparsec]-[examples]-[sql] がある。
jparsecからダウンロードしてきたファイルを解凍しておきます。
junitのjarファイルも手元になければ、ダウンロードしてきます。
ダウンロードしてきたjunitはjunit-4.18.jarとかバージョン名がついているので、
junit.jarという名前にかえておきます。
junitはjparsecのlibフォルダに格納しておきます。
では、eclipseがわの準備を行ってみましょう。
Eclipseに新規にJavaプロジェクトを作成します。
ファイルメニューのインポートで先ほど解凍してできたフォルダを選択し、それをプロジェクトのsrcディレクトリを指定してとりこみます。
srcフォルダは、4つあります、本体用、本体test用、example用、exampleテスト用
インポート直後は
まだ、プロジェクトのビルドパスにjarが登録されていませんので、コンパイルエラーになっています。
そこで、ビルドパスの設定でparsecのlibフォルダ内のjarをすべて登録します。
コンパイルエラー表示はほぼ消えます。
が、1カ所だけAllTest?クラスでエラーになっています。
それは、作者がライブラリをあげたくないからだと build.xmlの80行目に明記してありました。
こんな感じ、
AllTests uses jtc, which is an extra dependency that I don't want to upload.
おそらく、Androidのソースを流用したコードだから、著作権の問題であげれないとでも思ったのでしょうか。
それはさておき、
build.xmlには、このクラスのみ除外してコンパイルする記述がありました。
要するにいらないんです。この
だからbuild.xmlをいじくりたくなかったので、つぎのようにクラスを書き換えておきました。
package org.codehaus.jparsec; //import org.openqa.jtc.junit.TestSuiteBuilder; import junit.framework.TestSuite; /** * * @author benyu */ public class AllTests extends TestSuite { public static TestSuite suite() { //return TestSuiteBuilder.suite(AllTests.class); return null; } }
build.xmlがあるので、toolは、ソースが公開されていないみたいなので、開発元の方用のantタスクかもしれません。それ以外はコンパイルできました。
exampleのテストケースをみると使い方が書いてありました。
package org.codehaus.jparsec.examples.sql.parser;
RelationParserTest?
public void testSelect() {
SQLの問い合わせ文
select distinct 1, 2 as id from t1, t2
が下記のようにクラスの構造に解析されているのを確認しているテストコードが書かれていました。
Parser<Relation> parser = RelationParser.select(NUMBER, NUMBER, TABLE); assertParser(parser, "select distinct 1, 2 as id from t1, t2", new Select(true, Arrays.asList(new Projection(number(1), null), new Projection(number(2), "id")), Arrays.asList(table("t1"), table("t2")), null, null, null));
文字コードをEUCにしないと文字化けします。
http://www.lab2.kuis.kyoto-u.ac.jp/~hanatani/tmp/Parsec.html