2009.07.10
AndroidでリフレクションとJavaCCを試す(後編)
前半ではリフレクションの使い方についてざっと触れました。
後半では実際にリフレクションを使い、電卓に数学関数を組み込みます。
電卓プログラムについては土日でつくるコンパイラに詳細が載っています。
おもに変更する箇所は、プログラムを構成する最小単位を定義している箇所で、
今までは数字か、かっこ文を定義していた箇所です。
まずは関数呼び出し文を定義します。
関数名(引数, 引数..)といった感じになるように、以下のように文法を定義します。
<FUNCTION_NAME> “(” Exp() (”,” Exp())*”)”
次に情報の取得のために、Java部分で
受け皿となる変数を用意してやります。
import java.util.ArrayList;
…
Token t;
ArrayList arguments = new ArrayList();
情報を取得するために、文法定義にアクションを追加します。
t=<FUNCTION_NAME>
“(”
tmp = Exp(){arguments.add(tmp);}
(”,” tmp = Exp(){arguments.add(tmp);})*
“)”
あとはリフレクションを使って、必要なメソッドを呼び出すだけです。
今回はすべての変数をdouble型で保持することにしました。
なので、呼び出すべきメソッドの引数をすべてdouble型とし、引数の数だけを動的に決めることとしました。
String functionName = t.image;
Class[] argumentType = new Class[arguments.size()];
for(int i=0; i<argumentType.length; i++){
argumentType[i] = Double.TYPE;
}
Method mt = Math.class.getMethod(functionName, argumentType);
ret = (Double)mt.invoke(Math.class, arguments.toArray(new Double[0]));
その他、以前の回から小改修をいくつか施しています。
・字句解析部分に関数名FUNCTION_NAMEを定義
・字句解析部分に”,”を追加
・負の数(-1など)を記述可能に
など。
完成したソースコードを以下に示します。
options{
STATIC=false;
}
PARSER_BEGIN(Calc)
package takrock.den.kernel;
import java.util.ArrayList;
import java.lang.reflect.*;
import java.io.ByteArrayInputStream;
public class Calc{
public static void main(String[] arg) throws Exception{
Calc calc = new Calc(System.in);
calc.CompilationUnit();
}
public static double exp(String fomula) throws Exception{
ByteArrayInputStream bin =
new ByteArrayInputStream(fomula.getBytes());
Calc calc = new Calc(bin);
return calc.Exp();
}
}
PARSER_END(Calc)
SKIP : {
” ”
}
TOKEN:{
<Num:(<DNZ>(<D>)* (<POINT>(<D>)*)?) | <ZERO>>
|<#D: ["0"-"9"]>
|<#DNZ:["1"-"9"]>
|<#ZERO:["0"]>
|<NL:”\n” | “\r”>
|<#ALPHA: ["a"-"z"]>
|<FUNCTION_NAME:<ALPHA>(<ALPHA>|<Num>)+ >
|<#POINT:”.”>
}
TOKEN:{
<PLUS:”+”>
|<MINUS:”-”>
|<MULTI:”*”>
|<DIV:”/”>
|<LPAREN:”(”>
|<RPAREN:”)”>
|<COMMA:”,”>
}
void CompilationUnit()throws Exception:{
double i;
}{
i=Exp()<NL>
{
System.out.println(i);
}
}
double Exp()throws Exception:{
boolean plus=true;
double l=0, r=0;
}{
l=MulExp()[("+"|"-"{plus=false;})r=MulExp()]
{
return plus? l+r : l-r;
}
}
double MulExp()throws Exception:{
boolean mul=true;
double l=1, r=1;
}{
l=Primary()[("*"|"/"{mul=false;})r=Primary()]
{
return mul? l*r : l/r;
}
}
double Primary()throws Exception: {
Token t=null;
double ret;
double tmp;
ArrayList arguments = new ArrayList();
boolean minus = false;
}{
([<MINUS>{minus = true;}] t=<Num>{
double value = Double.parseDouble(t.image);
if(minus){
value *= -1;
}
ret = value;
}
| “(” ret=Exp() “)”
| t=<FUNCTION_NAME>
“(”
tmp = Exp(){arguments.add(tmp);}
(”,” tmp = Exp(){arguments.add(tmp);})*
“)”
{
String functionName = t.image;
Class[] argumentType = new Class[arguments.size()];
for(int i=0; i<argumentType.length; i++){
argumentType[i] = Double.TYPE;
}
Method mt = Math.class.getMethod(functionName, argumentType);
ret = (Double)mt.invoke(Math.class, arguments.toArray(new Double[0]));
}
)
{
return ret;
}
}
この電卓をAndroidアプリに積んで、動くのかを確認してみます。
Androidの開発環境は、
Eclipse 3.4 + Android SDK 1.5 r2
です。
Androidプロジェクトを作成し、/res/layout/main.xmlを編集します。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:text="Result shown here" android:id="@+id/resultTextView" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView> <EditText android:text="pow(cos(1),2)+pow(sin(1),2)" android:id="@+id/inputTextArea" android:layout_width="fill_parent" android:layout_height="wrap_content"></EditText> <Button android:text="Calculate" android:id="@+id/calcButton" android:layout_width="fill_parent" android:layout_height="wrap_content"></Button> </LinearLayout>
ここでは、数式入力用欄(EditText)と計算実行ボタン(Button)、結果出力部分(TextView)を定義しました。
次にアクションを追加します。Activityクラスを拡張して、GUIの挙動を定義してやります。
といっても計算実行ボタンを押した際に数式入力欄から文字列を取得し、電卓に計算させた結果を結果出力部分に表示してやるだけです。
package takrock.den;
import takrock.den.kernel.Calc;
import android.app.*;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.*;
public class Dentaku extends Activity implements OnClickListener{
TextView resultView;
EditText input;
Button calcButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
resultView = (TextView) findViewById(R.id.resultTextView);
input = (EditText) findViewById(R.id.inputTextArea);
calcButton = (Button) findViewById(R.id.calcButton);
calcButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
String text = input.getText().toString();
try{
double value = Calc.exp(text);
resultView.setText("result="+value);
}catch(Throwable e){
resultView.setText("error.");
}
}
}
まずはfindViewByIdメソッドを使ってGUI部品を取得します。
resultView = (TextView) findViewById(R.id.resultTextView);
input = (EditText) findViewById(R.id.inputTextArea);
calcButton = (Button) findViewById(R.id.calcButton);
DentakuクラスにOnClickListenerを組み込んでイベントを受信可能にし、
ボタンのイベントリスナとして設定します。
calcButton.setOnClickListener(this);
最後にOnClickListener.onClickを実装します。
入力欄inputから文字列を取得して、電卓クラスCalcに渡しています。
double value = Calc.exp(text);
最後に計算結果をresultViewオブジェクトに表示させます。
resultView.setText(”result=”+value);
というわけで、完成!
動かしてみると・・
あっさり、動いちゃってます。
うまくいかない→セキュリティ設定などをを変更→やった!
みたいな流れを書きたかった(期待していた)んですが、まあ動いているので何より、ということで。
今回はJavaCC+リフレクションをAndroid上で実行することを確認することができました。
少し改造すれば、電卓プログラムを再コンパイルせずとも、
ユーザがJavaで定義したメソッドを電卓で使うことが可能になります。
さらにがんばれば言語インタプリタなど、いろいろなものが作れそうですね!
[...] AndroidでリフレクションとJavaCCを試す(後編) [...]
Posted at 2009.07.10 7:13 PM by » AndroidでリフレクションとJavaCCを試す(前編): エスキュービズム ラボ Blog