0xdeadbeef

Java 最高!!!

2014年05月

テンプレートエンジンをなんとなく書いてる。

書きやすさとしては、C++やらCで書くよりはよほど書きやすい。

enumがstringifyできるから、そこは便利だなぁ。

意外とパーサーに複雑な正規表現は使わないので、あまり違和感はない。バックトラック部分は、try-with-resourcesで実装すればいいので、意外と楽。

複数のルールに順番にマッチさせていく、みたいなコードが存外面倒。まぁ、別にいいけど。

具体的には、atomを見る時に、数字と文字列とでどっちにするか選ぶ、なんて部分はわりと冗長。

とはいえ、簡単な数字リテラルを埋めるとかはできるようになってきた。

文字列リテラル、文字列連結演算子、IF FOR, WHILE, UNLESS あたりをやらないといけない。

結構あるなぁ。。このへんは、もうただ単に気合で実装して行けばなんとかなる感じ。

途中でモチベーション高めるために、どっかでテンプレートローダー先に書くかも。

INCLUDE, WRAPPER あたりは、まぁむずかしくはないよねぇ。

JavaでRAII的な事をやる場合、try-with-resources 使ってやれば、.close メソッドを自動的に呼んでくれるので、意外とよい。

dynamic scope 的な事をするには、このへんよしなにすれば良さそうね。

ANTLR良くできてるし、ちゃんと動いてくれてるときは楽なんだけど、エラーになったりしたときに、原因調べるのが面倒。

具体的には [ 以外にマッチするパターンを作るのがうまくいかなくて諦めた。

結局、手書きの方が、問題なくて楽でよい。

Antlr さん、C 用のやつはランタイム必要なのがうざくて使ったことなかったけど、Java ならまぁありかなというところで試す。

`brew install antlr4` とかすると antlr4 コマンドが使えるようになる。

以下のような、サンプルのものをちょっと変えたような文法を書きます。



grammar Expr;
prog:	expr NEWLINE*;
expr:	left=expr op=('*'|'/') right=expr
    |	left=expr op=('+'|'-') right=expr
    |	atom=INT
    |	'(' expr_=expr ')'
    ;
NEWLINE : [\r\n]+ ;
INT     : [0-9]+ ;



antlr4 コマンドで java ファイルを生成させる。maven plugin でもできるけど、maven plugin はなんかうまく動かなかった。maven plugin はなんか大体うまく動かないので、最初から Makefile でも使った方がいい感じある。



今回はMakfefile に以下のように書いた。



src/main/java/com/example/ExprParser.java: src/main/java/com/example/Expr.g4
        antlr4 -package com.example src/main/java/com/example/Expr.g4

あとは Visitor で、巡回しつつ、適当にスタックに積んだり計算したりすればいい。簡単ですね。

package antlrsample;

import static org.junit.Assert.assertEquals;

import java.util.Stack;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.misc.NotNull;
import org.junit.Test;

import com.example.ExprBaseListener;
import com.example.ExprLexer;
import com.example.ExprParser;

public class ExprTest {

	class Listener extends ExprBaseListener {
		public int ret;
		public Stack<Integer> stack = new Stack<>();

		@Override
		public void exitProg(@NotNull ExprParser.ProgContext ctx) {
		}

		@Override
		public void exitExpr(@NotNull ExprParser.ExprContext ctx) {
			if (ctx.expr_ != null) {
				// do nothing
			} else if (ctx.op != null) {
				int r = stack.pop();
				int l = stack.pop();

				switch (ctx.op.getText()) {
				case "*":
					stack.push(l * r);
					break;
				case "+":
					stack.push(l + r);
					break;
				case "/":
					stack.push(l / r);
					break;
				case "-":
					stack.push(l - r);
					break;
				default:
					throw new RuntimeException("Should not reach here");
				}
			} else if (ctx.atom != null) {
				stack.push(Integer.parseInt(ctx.atom.getText()));
			}
		}

		public int getResult() {
			if (stack.size() != 1) {
				throw new RuntimeException("Should not reach here");
			}
			return stack.firstElement();
		}
	}

	private int calc(String src) {
		ExprLexer lexer = new ExprLexer(new ANTLRInputStream(src));
		ExprParser parser = new ExprParser(new CommonTokenStream(lexer));

		Listener listener = new Listener();
		parser.addParseListener(listener);
		parser.prog();
		return listener.getResult();
	}

	@Test
	public void test() {
		assertEquals(116, calc("58*2"));
		assertEquals(3, calc("1+2"));
		assertEquals(11, calc("3+2*4"));
		assertEquals(7, calc("14/2"));
		assertEquals(20, calc("(3+2)*4"));
	}

}

Antlr4 は、一式揃ってる感じあるので、DSL 的なものとか設定ファイルとかをちゃちゃっと書くにはよさそう。

複雑な正規表現を Java で書くのつらいなーとか思い始めたら、気軽に Antlr4 に fallback するのが良さげ(具体的にはPerl で //x を使うようなケースでは Antlr4 に行くのがよさそう)。

このページのトップヘ