妄想プログラマのらくがき帳 : 2012

2012年12月18日火曜日

[Android]Windowsでいうところのiniファイル

アプリの設定や前回終了時の状態などを保存する手軽な方法として、
AndroidにはSharedPreferencesがあります(Windowsでいうところのiniファイルのようなものです)。

  • データを保存する

データを保存するには、SharedPreferences.Editorを使います。
//Activity.getPreferences()でSharedPreferencesを取得
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
//SharedPreferences.Editorを取得
Editor editor = preferences.edit();

//Editorを用いてデータを編集
//Key=MyKey,Value=Helloを保存
editor.putString("MyKey", "Hello");

//削除するときは、remove()に削除するデータのkeyを指定
//editor.remove("MyKey");

//最後にcommit()しないと編集が確定されず、保存されないので注意
editor.commit();

  • データを読み込む

データを読み込むには、SharedPreferencesを使います。
//Activity.getPreferences()でSharedPreferencesを取得
SharedPreferences preferences = getPreferences(MODE_PRIVATE);

//Keyを指定して値を取得
//第2引数には、指定したKeyが見つからなかった場合に返されるデフォルト値を指定
String myKeyValue = preferences.getString("MyKey", "");

2012年12月13日木曜日

[Android]マニフェストファイルのapplicationの子要素に<meta-data>を含める。

公式のドキュメントには載っていませんが、
マニフェストファイルのapplicationの子要素に<meta-data>を含めることが出来ます。

例えば、applicationの子要素に、ログレベルを指定する<meta-data>を追加するとします。
<meta-data
    android:name="com.example.androidsample.log_level"
    android:value="4" />

設定したデータは以下のように実行時に参照できます。
ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
       this.getPackageName(), PackageManager.GET_META_DATA);

int logLevel = appInfo.metaData.getInt("com.example.androidsample.log_level");
logLevelには4が入ります。

結構いろいろな使い道がありそうですが、公式のドキュメントに載っていないというのが最大のネックですね。
ドキュメントに載っていない=サポート外と考えられるので、APIのバージョンが変わったら
急に使えなくなるとかありそうですし。。。

2012年12月9日日曜日

[Android]Logレベルの設定

Androidでは、LogレベルをTAGごとに設定できます。
特定のTAGに設定されているLogレベルは、Log.isLoggable(String tag, int level)で確認できます。
例えばTAGが"MyActivity"の場合、

Log.isLoggable("MyActivity",  Log.VERBOSE)  ⇒  False
Log.isLoggable("MyActivity",  Log.DEBUG)  ⇒  False
Log.isLoggable("MyActivity",  Log.INFO)  ⇒  True
Log.isLoggable("MyActivity",  Log.WARN)  ⇒  True
Log.isLoggable("MyActivity",  Log.ERROR)  ⇒  True

となれば、TAG"MyActiviti"のLogレベルはINFOです。

AndroidのデフォルトLogレベルはINFOなので、
特に何も設定しない場合、isLoggable()は上記のような結果になります。
このLogレベルですが、変更する方法として次の2つの方法があります。

1.adbで変更する

adbで以下のコマンドを実行することで、Logレベルを変更できます。
adb shell setprop log.tag.MyActivity DEBUG
この場合、TAG"MyActivity"のLogレベルがDEBUGに設定されます。

2.local.propで変更する

/data/local.propに次の1行を追加します。(local.propが無ければ作成する)

log.tag.MyActivity=VERBOSE

この場合、TAG"MyActivity"のLogレベルがVERBOSEに設定されます。

以上の2つの方法でLogレベルを変更できるのですが、
設定できるLogレベルは、VERBOSE、DEBUG、INFOのいずれかのようです。
WARNやERRORに設定しようとしてもINFOに設定されてしまいます。

Logレベルの設定方法がわかり、これでLogの出力制御ができる!
っと思いきや、実はこれだけでは出力制御はできません。
なぜならば、Log.v()やLog.d()などのメソッドはレベルに関係なくログを出力してしまうからです。
何のためのLogレベルだよ!って感じですが、そういう仕様みたいです。
なので、出力制御するには以下のような処理をかます必要があります。
if (Log.isLoggable("MyActivity", Log.DEBUG)) {
 Log.d("MyActivity", "ログ・メッセージ");
}
実際に使う場合は、上記のような処理をまとめたLogクラスを作成して使うことになると思います。

しかし、何故こんな仕様なんだろう。。。


2012年11月20日火曜日

[Android]文字列の外部化

アプリで使用する文字列をハードコーディングせずに外部リソース化すると、以下のような利点が得られます。
  • アプリ内の文字列を一元管理できる(変更が容易になる)
  • 複数の言語に対応できる(多言語化)
文字列を変更することはよくあることので、多言語対応の予定が無くても外部リソース化しといた方が後々便利です。

文字列をリソースファイルに追加する

Androidのプロジェクトを新規作成すると、すでに文字列リソースファイルがプロジェクトに含まれています。res/values/strings.xmlがそのファイルになります。



ファイルを開くと「リソース」タブの画面(上の画像の画面)が表示されます。
この画面でも文字列をリソースファイルに追加できるのですが、文字列を追加するだけなら直接xmlファイルをいじった方が簡単なので、今回はxmlファイルを編集することにします。

「string.xml」タブを選択すると、次の画面が表示されます。



<resource></resource>の間に<string name="[string_name]">[文字列]</string>を追加します。
[string_name]にはプログラム中から文字列を参照するときの名前、[文字列]には表示する文字列を指定します。
Hello android!
↑のような要素を追加すると、プログラム中で getString(R.string.display_message) とすることで、"Hello android!"が取得できるようになります。

2012年11月15日木曜日

[VBA]VBAの実行速度を劇的にアップさせる。

最近VBAを使う機会があったので、そのときに使った実行速度向上手法を紹介します。

通常、セルの値を設定/取得する場合、以下のようなコードになります。
Sheets(1).Cells(1, 1).Value = "A"
tmp = Sheets(1).Cells(1, 1).Value
特定の列のデータを順に取得して…といった場合は、ループを使って以下のようなコードになると思います。
For i = 1 To 10
    tmp = Sheets(1).Cells(i, 1).Value
    
    'Do something
Next
この場合、ループ回数が10回なのでそれ程気になりませんが、100、200、…とループ回数が増えるに従って非常に時間が掛かるようになります。

当然、Application.ScreenUpdating = Falseにしたりしますが、それでも処理が重たい場合があります。
そのような場合、以下のようにするとループを使う箇所の処理が、ちょっぱや!になります。
'ループで処理する値を取得/設定する範囲のRangeを取得
With Sheets(1)
    Set targetRange = .Range(.Cells(1, 1), .Cells(10, 1))
End With

'Rangeのデータを配列で取得
Dim values()
values = targetRange.Value

'配列に取得したデータで処理を行う
For i = 1 To 10
 tmp = values(i, 1)

 'Do something
Next

'値を設定するときは配列をRangeに一括設定
targetRange.Value = values
たったこれだけですが、実行速度は劇的に向上します。
試しに適当な処理で処理時間を計測してみました。

以下のコードで計測した結果…
Const LoopCount = 10000

Private Sub CommandButton1_Click()
    Application.Cursor = xlWait
    Application.ScreenUpdating = False
    
    'ループ Ver
    t = Timer
    For i = 1 To LoopCount
        tmp = Sheets(1).Cells(i, 1).Value
        Sheets(1).Cells(i, 2).Value = tmp * 10
    Next
    Debug.Print ("ループで1セルずつ取得/設定 : " & (Timer - t))
    
    'Range Ver
    t = Timer
    With Sheets(1)
        Set srcRange = .Range(.Cells(1, 1), .Cells(LoopCount, 1))
        Set dstRange = .Range(.Cells(1, 3), .Cells(LoopCount, 1))
    End With
    
    Dim values()
    values = srcRange.Value
    For i = 1 To LoopCount
        values(i, 1) = values(i, 1) * 10
    Next
    dstRange.Value = values
    
    Debug.Print ("Rangeで一括取得/設定 : " & (Timer - t))
    
    Application.ScreenUpdating = True
    Application.Cursor = xlDefault
End Sub
Debug.Print の出力結果は、

ループで1セルずつ取得/設定 : 0.4609375
Rangeで一括取得/設定 : 0.015625

なんと約30倍もの実行時間差が出ました!
実際、今までかなり時間が掛かっていた箇所を上記の方法で修正したところ、驚くほど速くなりました。
VBAで「遅いなぁ…」「もう少し速くならないかぁ…」とお悩みのあなた、この方法をぜひ試してみてください!

2012年11月12日月曜日

[Android]画面遷移を実装する。

今回は画面遷移を実装してみます。

要求仕様は次の3点。
  • メイン画面のボタンを押下すると、サブ画面に遷移する
  • サブ画面は画面中央にメッセージを表示する
  • 画面遷移の際に、メイン画面からサブ画面に対して表示するメッセージを渡す
画面遷移と画面間のデータの受け渡しを勉強するのが目的です。

まずは、サブ画面を作成します。
メニューの[ファイル] - [新規] - [その他]から[Android] - [Android Activity]を選択すると、
新規アクティビティの作成ウィザードが表示されるので、画面に各項目を入力してサブ画面の
アクティビティを作成します。

作成するサブ画面はシンプルなものなので、BlankActivityを選択し、名前をSecondActivityとしました。



「完了」ボタンを押すと、srcフォルダ下のパッケージにSecondActivity.javaが追加されます。
この時点でSecondActivityにはTextViewが配置されているので、これを使ってメッセージを表示しようと思います。
とりあえず、レイアウトエディタ画面でTextViewにid="@+id/textView"を設定しておきます。

次は、メイン画面のボタンクリックハンドラに次画面を表示するコードを記述します。
public void onClickButton(View v)
{
 Intent intent = new Intent(this, SecondActivity.class);
     startActivity(intent);
}
Intentというのは各コンポーネントを結びつける役割をするものです。
上記のようにstartActivity()に渡すことで、Intentコンストラクタの第2引数に指定したActivityを開始させることができます。

最後に、メイン画面からサブ画面にメッセージを渡します。
メイン画面側は、先ほどのボタンクリックハンドラに1行追加します。
public void onClickButton(View v)
{
 Intent intent = new Intent(this, SecondActivity.class);
 intent.putExtra("message", "Hello SecondActivity.");
     startActivity(intent);
}
putExtra()は所謂key-valueペアです。上記の場合、keyを"message"としてメッセージを格納しています。

サブ画面側は、putExtra()で格納したメッセージを取得し、画面上のTextViewに表示します。
メッセージの取得と画面への表示は、画面表示と同時に行いたいので、サブ画面のOnCreate()の中に記述します。
public void onCreate(Bundle savedInstanceState)
{
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_second);
       
 Intent intent = getIntent();
 TextView textView = (TextView)findViewById(R.id.textView);
 textView.setText(intent.getStringExtra("message"));
}
メイン画面でstartActivity()に渡したIntentは、getIntent()で取得することができます。
また、Intent.putExtra()で格納したメッセージはIntent.getStringExtra(key)で取得することができます。
上記のコードでは、先にメッセージ表示先のTextViewをidを使って取得し、Intent.getStringExtra(key)で取得した
メッセージをTextView.setText()で画面に表示しています。

これで実装完了です。
アプリを起動し、メイン画面のボタンを押下すると…



サブ画面が表示され、メイン画面から渡したメッセージが表示されました。

2012年11月6日火曜日

[Android]ボタンのクリックイベントを実装する。

UIの基本パーツであるボタン。そのボタンにクリックイベントを実装する方法です。

まずはアクティビティにボタンを配置します。
[res] - [layout]から、ボタンを配置するアクティビティのxmlファイルを開きます。



xmlファイルを開くと、レイアウトのエディット画面が表示されます。
レイアウトのエディット方法はGUIを使う方法とxmlファイルを直接編集する方法があり、
エディット画面下側にある2つのタブ([Graphical Layout]タブと[xmlファイル名]タブ)で
切り替えられます。

今回はGUIを使ってボタンを配置しました。
配置方法は簡単で、エディット画面左側のPaletteから[Form Widgets]にある[Button]を、
エディット画面上のアクティビティにドラッグ&ドロップするだけです。
位置やサイズはドロップ後にマウスで調整します。

次にボタンにクリックイベントを設定します。
VisualStudioならエディット上のボタンをクリックするだけで、
イベントハンドラのスケルトンを作成し、それをボタンのイベントハンドラとして設定してくれますが、
Androidアプリを作る時は、自前でハンドラを作成し、ボタンに設定する必要があります。
(ちょっとメンドイですね。。。)

まずはハンドラを作成します。
アクティビティのコードに下記のクリックハンドラを追加します。

public void onClickButton(View v)
{
  AlertDialog.Builder alertDialog = new AlertDialog.Builder(MainActivity.this);
     alertDialog.setTitle("タイトル");
     alertDialog.setMessage("Hello android!");
     alertDialog.show();
}

ハンドラ内ではアラートダイアログを表示しています。
次にボタンに作成したハンドラを設定します。
レイアウトのエディット画面に戻り、ボタンを選択状態にし、ボタンのプロパティを表示します。
プロパティ内のViewグループの中に「On Click」の項目があるので、そこに先ほど作成したハンドラの
メソッド名を入力します。



これでハンドラをボタンに設定することが出来ました。
プログラムを起動し、ボタンをクリックすると、アラートダイアログが表示されます。





2012年10月29日月曜日

AndroidアプリでHello World.

スマホに買い換えてから早5ヶ月が過ぎ去ろうとしています。
なのに、今まで1個もAndroidアプリを作ったことがない!
せっかくのスマホに買い換えたのに、これは勿体ないということで、
Androidアプリを作ってみようと思いったった次第です。
とりあえずHello Worldから始めてみようと思います。

まずアプリを作成するには、最初に開発環境を整える必要があります。
下記の場所からSDKをダウンロードして
Get the Android SDK
そのすぐ下のInstalling the SDKに従って環境をインストールします。
Installing the SDK
Eclipseを使ったことがあれば、インストールはそんなに難しくないと思います。

環境が整ったら早速Hello Worldを表示してみます。
まずはEclipseでプロジェクトを作成します。
メニューの[ファイル] - [新規] - [Android Application Project]をクリックすると、
下記の画面が表示されるので、各項目を入力していきます。



Application Nameはアプリの名前です。ユーザーが目にする名前になります。
Project Nameはプロジェクトディレクトリの名前です。Eclipseに表示される名前でもあります。
Package Nameはアプリのパッケージ名です。

Build SDKはアプリのコンパイルするときに使用するSDKのバージョンです。
Minimun Required SDKはアプリが動作するために最低限必要なSDKのバージョンです。

今回はHello Worldを表示するだけなので、Application Nameに"AndroidSample"を入力し、
その他の項目は自動入力された値とデフォルト値のままとしました。

「次へ」ボタンを押すと、Iconの設定画面が表示されます。
今回はとりあえずそのままにしといて、「次へ」ボタンを押して次へ進みます。

Create Activity画面では、アクティビティクラスを作成するかを設定します。
アクティビティクラスはC#のFormのようなものっぽい?ので、BlankActivityを作成することにしました。

New Blank Activity画面では、アクティビティクラスの詳細を設定します。
...が、僕の環境では何故かNew Blank Activity画面の下側がCreate Activity画面のままとなり、
入力欄が切り替わってくれません(´;ω;`)
とりあえず「完了」ボタンは押せるので、ボタンを押して完了しました。

作成したプロジェクトの[res] - [layout]フォルダには、activity_main.xmlが作成されており、
これがメイン画面になります。画面中央にHello world!が書かれているので、そのままアプリを起動させます。

メニューの[実行] - [実行]の次を実行画面からAndroid アプリケーションを選択し、「OK」ボタンを
押すとアプリが起動します(マシンによっては、かなり時間が掛かります)。

メイン画面が表示され、画面中央に"Hello world!"が表示されました!



Visual Studioと比べるとちょっとメンドイですが、WinApiとかに比べると比較的簡単に
画面を作成することが出来るみたいです。

2012年10月21日日曜日

Irony コメントを文法に追加する。

今回もIronyネタ。

コメントを文法に追加する方法として次の2通りの方法があります。

1.GrammarクラスのNonGrammarTerminals変数に、コメントを表すTerminalクラスを追加する。

GrammarクラスのNonGrammarTerminalsに追加したTerminalは、
文字列をパースする際に無視されるようになります。ParseTreeにも追加されなくなります。
以下が自作GrammarクラスのコンストラクタでコメントをNonGrammarTerminalsに追加する例です。
//コメントを定義
var comment = new CommentTerminal("comment", "//", "\n", "\r");
//コメントを非文法終端記号として追加
this.NonGrammarTerminals.Add(comment);

2.コメントを表すTerminalを文法の1部として扱う

ParseTreeでコメントを扱いたい場合があります。
コメントからドキュメント生成したい!といった場合などです。
そのような場合、1の方法だとParseTreeにコメントが追加されないので不都合です。
こんな時はコメントを文法の中に組み込む方法を使います。
//コメントを定義
var comment = new CommentTerminal("comment", "//", "\n", "\r");
//コメントを文法の一部として追加
ProgramLine.Rule = Statement + ToTerm(";") | comment;
Program.Rule = MakeStarRule(Program, ProgramLine);
こっちの方法だとParseTreeにコメントが追加されます。

2012年10月15日月曜日

Irony ParseTreeに不要な項目を追加しないようにする。

IronyでParser.Parse()を使いParseTreeを構築すると、
1ステートメントの区切りである";"(セミコロン)や、単なる区切りの","(カンマ)といった文字まで、
1つのノードとしてParseTreeに追加されます。

以前のエントリーで定義した文法クラスで作成したParseTree。
画面右側のParseTreeに"; (Key symbol)"というノードが確認できます。














多くの場合、これらのノードはその後の処理において不要なノードであり、
ParseTreeを走査するうえで邪魔なノードになります。
なので、指定した項目をノードに追加しないようにする方法があったら便利ですよね。

そんな「あったらいいなぁ」な方法が、Ironyにはちゃんと用意されています。

方法はとっても簡単です。
文法クラスのコンストラクタでMarkPunctuation()を呼び出して、
ParseTreeに追加しない項目を登録するだけです。

下の例では、";"(セミコロン)をParseTreeに追加しないようにする命令です。
this.MarkPunctuation(";");
この命令をコンストラクタに追加した文法クラスでParseTreeを作成してみると…












画面右側のParseTreeから"; (Key symbol)"というノードが消えているのが分かります。

文法が複雑になればなるほど不要なノードが増えてくるので、MarkPunctuation()をうまく使って、
綺麗なParseTreeが作成されるようにしときましょう。

2012年10月13日土曜日

IronyのGrammarExplorerを使って文法をテストする。

IronyにはGrammarExplorerというツールがあります。
GrammarExplorerを使うことで定義した文法クラスを簡単にテストすることができます。

IronyとGrammarExplorerはこちらからダウンロードできます。
Irony - .NET Language Implementation Kit
Ironyのソリューションファイルの中にGrammarExplorerが含まれています。

GrammarExplorerを起動すると次の画面が表示されます。












画面左上のGrammarコンボボックスの右側にあるボタンでAdd grammarを選択すると、
文法クラスを含むアセンブリを選択するダイアログが表示されます。
初期表示ではDllファイルしか選択できないように見えますが、
ファイル名を直接入力すればexeファイルも選択できます。

アセンブリを選択するとアセンブリに含まれる文法の一覧が表示されるので、
テストしたい文法を選択します。













今回は以前のエントリーで定義したサンプル文法クラスをテストしてみます。

文法を選択すると、終端文字一覧や非終端文字一覧、パーサの状態一覧が各タブページに表示されます。
これらを見れば、ちゃんと意図した通りに定義できているのか確認することができます。

文法のテストで使用するのはTestタブです。
まず、Testタブページのテキストボックスにパース対象の文字列を記述します。
次にParseボタンを押すと、右側のParse Treeにパーシングによって生成されたTreeが表示されます。














Tree上でノードを選択すると、テキストボックスで該当する箇所が選択状態になります。
(ただし、選択状態になるのは該当箇所の先頭文字のみみたいです)
逆の機能もあります。Locate>>ボタンを押すとテキストボックス上のカーソル位置にあるノードが
Tree上で選択状態になります。

パース対象文字列に文法エラーがある場合、ParseTreeは表示されません。
代わりに画面下部のParser Outputにエラーとなった箇所とエラーメッセージが表示されます。
VSのエラー一覧のような感じに表示されます。

また、画面下部のParser TraceでEnable Traceにチェックを入れると、
パーサの動作をトレースするようになります。
チェックをいれてParseボタンを押すと、次のようなトレース情報が表示されます。














パーサの状態、スタックのトップ、入力トークン、パーサの動作が時系列に表示されるので、
パーサが文字列をパースする経過がよく分かります。

GrammarExplorerは複雑な文法を定義するときに欠かせないツールなので、
Ironyを使うなら使いこなせるようになっておきましょう!

2012年10月11日木曜日

C#のパーサジェネレータ Ironyを使ってみた。その3。

前回に引き続き、Ironyについてです。

前回は文法を定義する方法を書きました。今回は定義した文法で実際にパースしてみます。

まずはサンプルソース。
string src =
    "a = 10;" +
    "b = 20;" +
    "c = a + b;" +
    "c *= c;";

MyGrammar grammar = new MyGrammar();
Parser parser = new Parser(grammar);

ParseTree parseTree = parser.Parse(src);
MyGrammarは、前回定義した文法クラスです。
ご覧のとおり、パース処理自体は3行で書けます。

  1. 文法クラスのインスタンスを生成。
  2. 文法インスタンスを引数にパーサクラスのインスタンスを生成。
  3. パーサインスタンスのパーサメソッドに文字列を渡す。

たったこれだけです。
Parser.Parse()の戻り値であるParseTreeはASTになってます。
ParseTree.RootがASTのルートノードで、子ノードがParseTree.Root.ChildNodesに繋がっています。
このASTは様々な情報を持っているので、あんなことやこんなことに使えちゃいます^^

ParseTree.Root変数がnullの場合、パース失敗です。
パースエラーに関する情報は、ParseTreeクラスのParserMessages変数で得ることができ、
エラーとなったソース上の場所はParseTree.ParserMessages[ ].Location、
修正候補のリストはParseTree.ParserMessages[ ].ParserState.ExpectedTerminalsで取得できます。

Ironyを使ってみて感動したのは、凄く簡単に使えるようになる!ということです。
他のパーサジェネレータなどを使うには、文法定義に専用の文法を覚えないとダメな場合が多いですが、
IronyはBNFとC#の文法が分かっていれば、すぐに文法定義ができるようになります。

日本語の資料がほとんど無いのが難点ですが、C#でパーサジェネレータが必要な方は是非使ってみてください!

2012年10月9日火曜日

C#のパーサジェネレータ Ironyを使ってみた。その2。

前回に引き続き、Ironyについてです。

前回はプロジェクトの参照設定にIrony.dllを追加するところまで書きました。
今回はIronyで文法を定義する方法です。

Ironyで文法を定義するには、Irony.Parsing.Grammarを継承した文法クラスを作成し、
そのクラスのコンストラクタに文法を定義する処理を記述します。
下記のコードは試しに作ってみた簡単な文法クラスのコードです。

[Language("MyGrammar")]
public class MyGrammar : Grammar
{
    public MyGrammar() : base(true)
    {
        //
        //終端記号を定義
        //
        
        //数字
        var number = new NumberLiteral("number");
        number.DefaultIntTypes = new [] { TypeCode.Int32,
                                          TypeCode.Int64,
                                          NumberLiteral.TypeCodeBigInt };
        number.DefaultFloatType = TypeCode.Double;
        //識別子
        var identifier = new IdentifierTerminal("identifier");
        //コメント
        var comment = new CommentTerminal("comment", "//", "\n", "\r");

        //
        //非終端記号を定義
        //

        var Expr = new NonTerminal("Expression");
        var Term = new NonTerminal("Term");
        var BinExpr = new NonTerminal("BinaryExpression");
        var BinOp = new NonTerminal("BinaryOperator");
        var AssignmentStmt = new NonTerminal("AssignmentStatement");
        var AssignmentOp = new NonTerminal("AssignmentOperator");
        var Statement = new NonTerminal("Statement");
        var ProgramLine = new NonTerminal("ProgramLine");
        var Program = new NonTerminal("Program");

        //
        //文法を定義
        //

        Expr.Rule = Term | BinExpr;
        Term.Rule = number | identifier;
        BinExpr.Rule = Expr + BinOp + Expr;
        BinOp.Rule = ToTerm("+") | "-" | "*" | "/";
        AssignmentStmt.Rule = identifier + AssignmentOp + Expr;
        AssignmentOp.Rule = ToTerm("=") | "+=" | "-=" | "*=" | "/=";
        Statement.Rule = AssignmentStmt | Expr | Empty;
        ProgramLine.Rule = Statement + ToTerm(";");
        Program.Rule = MakeStarRule(Program, ProgramLine);
        //文法のルートを設定
        this.Root = Program;

        //
        //演算子の優先順位を定義
        //

        RegisterOperators(1, "+", "-");
        RegisterOperators(2, "*", "/");
        
        this.LanguageFlags = LanguageFlags.NewLineBeforeEOF |
                             LanguageFlags.SupportsBigInt;
    }
}
まず、6~9行目で終端記号を定義しています。
数字や識別子、コメントといった一般的な終端記号は、すでにクラスが用意してあるのでそれを使います。
他にもRegexLiteralやCustomTerminalなどといった終端記号用のクラスがあるので、必要に応じて使い分けます。

次に21~33行目で非終端記号を定義しています。
引数で与えた名前は、パースツリー内の各ノード名として使用されます。

35~49行目では文法を定義しています。
'+'と'-'が演算子オーバーロードされているので、BNFライクに記述することができます。
・定義に文字列リテラルを使う場合は、ToTerm()を使う
・'*'(繰り返し)を記述するには、MakeStarRule()を使う
といったところが、Irony独特の記述方法です。

最後に演算子の優先順位の設定とオプションの設定をしています。

以上で簡単な文法ですが定義完了です。
Irony付属のサンプルとかを見ると、C#の文法とかは定義がもっと複雑になるみたいですが、
上記のような簡単な文法なら簡潔に記述することができてイイ感じです^^
(複雑な文法定義に関しては、そのうち別エントリーで書く予定です)

次回は定義した文法でパーサを作成し、実際にパースしてみます。

2012年10月8日月曜日

C#のパーサジェネレータ Ironyを使ってみた。その1。

C#で簡単に使えるパーサジェネレータってないかな~と探していたところ、
Ironyが結構良さげだったので試しに使ってみました。

IronyはオープンソースのLALR(1)パーサジェネレータで、CodePlexで公開されています。
Irony - .NET Language Implementation Kit

Ironyの大きな特徴として、C#のコードで文法を定義できるという点があります。
下記のコードは、Irony - .NET Language Implementation Kitにあるサンプルの一部ですが、
こんな感じでC#で自然に文法を定義できます。

// 2. Non-terminals
var Expr = new NonTerminal("Expr");
var Term = new NonTerminal("Term");
var BinExpr = new NonTerminal("BinExpr", typeof(BinExprNode));
var ParExpr = new NonTerminal("ParExpr");
var UnExpr = new NonTerminal("UnExpr", typeof(UnExprNode));
var UnOp = new NonTerminal("UnOp");
var BinOp = new NonTerminal("BinOp", "operator");
var PostFixExpr = new NonTerminal("PostFixExpr", typeof(UnExprNode));
var PostFixOp = new NonTerminal("PostFixOp");
var AssignmentStmt = new NonTerminal("AssignmentStmt", typeof(AssigmentNode));
var AssignmentOp = new NonTerminal("AssignmentOp", "assignment operator");
var Statement = new NonTerminal("Statement");
var ProgramLine = new NonTerminal("ProgramLine");
var Program = new NonTerminal("Program", typeof(StatementListNode));

// 3. BNF rules
Expr.Rule = Term | UnExpr | BinExpr | PostFixExpr;
Term.Rule = number | ParExpr | identifier;
ParExpr.Rule = "(" + Expr + ")";
UnExpr.Rule = UnOp + Term;

また、ライセンスはMITライセンスなので、著作権・無保証の明示をしておけば利用や改変は自由です。
ちなみにIrony - .NET Language Implementation KitのSystem Requirementsに
>>Windows 7, .NET Framework 4.0, Visual Studio 2010
とありますが、VistaやVSExpress2010でも使えます(XPは確認していませんが、たぶん使えます)。

さてIronyの使い方ですが、まずはCodePlexのページのDOWNLOADSからzipファイルを落としてきます。
zipファイルの中にはIronyのソリューション一式が入ってるので、ソリューションを開きます。

※このときVSがExpressの場合、テスト関係のプロジェクトなど一部のプロジェクトが開けませんが、
 Ironyを使うだけなら問題ないです。ソリューションを開くごとにメッセージが表示されるので、
 うっとおしい場合は(利用不可)となっているプロジェクトをソリューションから削除しとくと楽です。

ソリューションが開けたら、ビルドを行いIrony.Dllを作成します(Irony\bin\Debug[or Release]に出力されます)。
この出来上がったDllを、パーサを使いたい(作りたい)プロジェクトの参照に追加したら準備完了です。

今回はここまで。次回は文法の定義の仕方について書きます。

2012年9月30日日曜日

テストエクスプローラーが壊れた。。。


Visual Studio Express 2012のテストエクスプローラーが突然開けなくなりました。
メニューの「テスト」-「ウィンドウ」-「テストエクスプローラー」を選択すると、
catchされない例外が発生し、Visual Studioが強制終了するという状態です。

インストールした直後は開くことができたのに…
現象が発生するまでの間にやったことの中で怪しげなのは

1.VS2008Proで作成した単体テストプロジェクトを含むソリューションを
   間違ってExpress2012で開いてしまった。
2.Team Foundation Server Express 2012をインストールし、アンインストールをした。

の2つですが、やっぱ1のProで作成したものをExpressで開いちゃったのが
不味かったんでしょうか。。

仕方ないので、とりあえずコントロールパネルのプログラムと機能から
修復をしてみました…が直らないorz

ちょっとメンドクサイけどアンインストールして再インストール。
これで直るだろうと思ってたんですが、なんとこれでも直らない!

こういう場合は大抵どっかに削除されなかった設定やファイルが残っているもの。
ということで、再度アンインストール後に、レジストリやProgram Filesフォルダを探し回って
Visual Studio 11.0とWDExpressに関する項目を全て削除してやりました。
その後、再インストール。落ちるなよ~と祈りながらテストエクスプローラーを開いてみると

開いたー^^

こんなことに3時間も使ってしまったorz

もし再インストールしても直らないような現象で困っているようでしたら、
残ってるゴミを手動で削除してから再インストールすると直るかもしれません。
アンインストールしても結構色んなものが残ってたりするんで…

注:レジストリをいじる時や消して良いか分からないファイルを削除するときは、
   ちゃんとバックアップをとってからやりましょう。

2012年9月24日月曜日

Visual Studio Express 2012がイイ!


9/13にVisual Studio Express 2012 for Windows Desktopがリリースされたので、
早速インストールして使ってます。
Express 2012をインストールするまではVisual Studio 2008 Professionalを使ってたんですが、
Express 2012が凄くイイ出来なので乗り換えちゃいました。

Visual Studio Express 2012は
http://www.microsoft.com/visualstudio/jpn/downloads
からダウンロードできます。

軽い!
まず起動してみてビックリ!凄く起動が速い。
VS2010の起動がもっさりしていたので、余計に速く感じました。

ソリューションエクスプローラーが凄い!
2012を使って一番驚いたのがソリューションエクスプローラーが凄く便利になったことです。

・クラス、メソッドが表示される
今まではソリューション内のファイルがツリーで表示されるだけでしたが、
2012ではファイル内のクラス、メソッド等も表示されるようになってます。
新しいソリューションエクスプローラー。クラスビューの機能が統合された感じです。 




















・選択された項目のプレビュー
エクスプローラー内でソリューションを選択すると、エクスプローラーのツールバーに
「選択された項目のプレビュー」ボタンが表示されます。

これを押してプレビュー機能を有効にしてエクスプローラー内のクラスやメソッドを選択すると、
エディタ上で選択した項目の定義場所に飛んでくれます。

選択した項目がエディタ上で表示される。









この機能のおかげてソース内の移動がすばやく行えるようになりました。


・エクスプローラー内の検索機能
エクスプローラー内の検索機能も追加されました。


















エクスプローラーにはクラスやメソッド、変数も含まれるようになったので、
これらも検索することができます。また、検索範囲は右クリックメニューの「ここまで検索」で
絞ることができます。

他にも、右クリックメニューからクラスの基本型/派生型/使用元を一覧表示できたり、
メソッドの呼び出し/呼び出し元/使用元を一覧表示できます。
あと地味に嬉しいのが右クリックメニューに「エクスプローラーでフォルダーを開く」が
追加されたことですね。
(今まではProfessionalにはあったんですが、Expressにはありませんでした)

コード分析ツールが追加された!
VS2008 ProfessionalやVS2010 Express上でコード分析を実行するには、
FxCop等のコード分析ツールを外部ツールとして登録する必要があって少し面倒でした。
しかし2012にはコード分析ツールが追加されているので、そんな手間がかかりません。
また、プロジェクトのプロパティの「コード分析」タブで「ビルドに対するコード分析の有効化」を
チェックすると、ビルド時にコード分析を実行してくれるようになります。

他にも色々と細かい部分した所が便利になっていて、凄くコーディングが捗ります。
これが無料だっていうんだから、凄いですよね~。
仕事でHEWを使ったりすると使いやすさの差に愕然とします。。。

2012年9月20日木曜日

Sandcastle Help File Builderを使ってみた。その2。

前回のエントリーに引き続いて、C#のドキュメントコメントを用いたドキュメント生成についてです。
今回はドキュメントコメントからどのようなドキュメントが出力されるか試してみました。

まず、以下のようなサンプルコードを書きました。
ドキュメントコメントの各タグの出力結果を確認するためのコードなので、処理内容は適当です。

/// <summary>
/// ここにクラスの概要。
/// </summary>
public class Program
{
    /// <summary>
    /// ここにプロパティの概要。
    /// </summary>
    /// <value>ここに値の説明。</value>
    public int MyProperty { get; set; }

    /// <summary>
    /// ここに概要を記述。
    /// </summary>
    /// <param name="args">引数。</param>
    public static void Main(string[] args)
    {
    }

    /// <summary>
    /// ここに概要を記述。
    /// </summary>
    /// <remarks>
    /// ここに詳しい説明。
    /// <para>段落を区切る場合はparaタグを使う。</para>
    /// <see cref="System.Int32"/>
    /// </remarks>
    /// <param name="value1">値1。</param>
    /// <param name="value2">値2。</param>
    /// <returns>value1にvalue2を加えた結果を返します。</returns>
    /// <exception cref="System.ArgumentOutOfRangeException">
    /// 引数が範囲外です。
    /// </exception>
    /// <exception cref="System.ArgumentNullException">
    /// 引数がnullです。
    /// </exception>
    /// <example>
    /// ここに使用例を記述。コード例はcodeタグ内に記述。
    /// <code>
    /// int result;
    /// result = Program.Add&lt;int, int&gt;(10, 5);
    /// </code>
    /// </example>
    /// <seealso cref="Program.Main"/>
    public static int Add(int value1, int value2)
    {
        return Program.AddHelper<int, int>(value1, value2);
    }

    /// <summary>
    /// Public以外は出力されない。
    /// </summary>
    /// <typeparam name="T1">***の型。</typeparam>
    /// <typeparam name="T2">***の型。</typeparam>
    /// <param name="value1">値1。</param>
    /// <param name="value2">値2。</param>
    /// <returns>value1にvalue2を加えた結果を返します。</returns>
    internal static int AddHelper<T1, T2>(int value1, int value2)
    {
        return value1 + value2;
    }
}

このコードをSandcastle Help File Builderで出力すると以下のようなドキュメントが作成されます。

クラスのドキュメント

MyPropertyのドキュメント













Add()のドキュメント
AddHelper()のドキュメント













ドキュメントコメント記述時の注意点
・タグの記述順は生成されるドキュメントレイアウトにも反映されるため、
   VSで///を入力したときの自動挿入の順序に従った方がmsdnと同じレイアウトになって○。
・ドキュメントコメント内の<>は&lt;&gt;で記述する。

実際に出力してみると各タグの役割がよく分かりますね。
振り返ってみると、今まで場所違い(タグ違い?)なコメントを記述をしていた気がします。。。

ちゃんと各タグの役割を理解して、より良いドキュメントが作成されるようなコメントを
記述するようにしましょう。じゃないと、わざわざドキュメントコメントで記述している意味がないですからね~。

2012年9月18日火曜日

Sandcastle Help File Builderを使ってみた。その1。


C#にはドキュメントコメントがあります。
特定のコードブロックの直前にXMLタグを記述することで、
コードのドキュメントを作成できるというものです。
(詳細はXMLドキュメント コメント(C# プログラミング ガイド)を参照のこと)

このドキュメントコメント、今までコーディング規約やC#標準ということで使ってきたけど、
ふと思い起こしてみると一度もドキュメント作成で使ったことがない。。。
そこで、ドキュメントコメントが実際にどんな感じのドキュメントになるのか実践してみました。

1.XMLドキュメントファイルを作成する。
ドキュメントを作成するには、まずドキュメントコメントからXMLドキュメントファイルを
作成する必要があります。方法は簡単で、
  • VisualStudioの場合
プロジェクトのプロパティで、ビルドタブの"XMLドキュメントファイル"の
チェックをONにしてビルド。
  • コマンドラインでコンパイルする場合
/doc:ファイル名のコンパイラオプションを使用。

これだけです。これでXMLドキュメントファイルが指定した場所に出力されます。

2.Sandcastle Help File Builderをインストールする。
1で作成したXMLドキュメントファイルですが、単なるXMLファイルです。
これをちゃんとしたドキュメントにするには、NDocやSandcastleといった
ドキュメント作成ツールを使います。今回はSandcastle Help File Builderを使って
ドキュメントを作成しようと思います。

Sandcastle Help File BuilderはCodePlexからダウンロードできます。
http://shfb.codeplex.com/
DOWNLOADSからSandcastle Help File Builder Installerをダウンロードしてください。

ダウンロードしたSHFBGuidedInstaller_***.zipを解凍したフォルダに、
SandcastleInstaller.exeがあるのでこれを実行。
あとはウィザードに従って必要なものをインストールします。

インストールは下記のブログが参考になります。
http://d.hatena.ne.jp/tueda_wolf/20120717/p3

3.Sandcastle Help File Builderでドキュメントを作成する。
インストールが完了したら、スタートメニューのすべてのプログラム-Sandcastle Help File Builderから
Sandcastle Help File Builder GUIを起動します。

起動したら、画面右側にあるProject Exploreにドキュメントを作成するexeファイルと
XMLドキュメントファイルを追加します。
Project Explore - Documentation Sourcesの右クリックメニュー[Add Documentation Source...]を
選択すると、ファイル選択ダイアログが開くので、対象のexeファイルを選択してください。

※XMLドキュメントファイルとexeファイルの拡張子以外の部分が同じなら、exeファイルを追加するだけで
 XMLドキュメントファイルも自動で追加されます。追加されない場合は、同じ方法でXMLドキュメントファイルも追加します。

次に画面左側のProject Propertiesで出力方法等の各種設定を行います。

プロパティ説明
Build
BuildAssemblerVerbosityビルドログの詳細度を設定。
BuildLogFileビルドログファイルを設定。
指定無しの場合、OutputPaehで指定したフォルダにLastBuild.logが作成される。
CleanIntermediatestrueならビルド成功時に中間ファイルを削除する。
ComponentConfigurationsビルドコンポーネントを設定。
Cached~を追加するとビルド時間を短く出来たりする。
CppCommentsFixupC++コンパイラは特定の状況で非標準のXMLコメントが生成することがあり、C++のメソッドのドキュメント化に失敗することがある。そのような場合、ここをtrueにすればドキュメント化可能になる場合がある。
DisableCodeBlockComponenttrueなら<code>タグで囲まれた部分のコードに対する色付けが無くなる。
FrameworkVersion対象の.NET Framework versionを設定。
HelpFileFormatドキュメントの出力形式を設定。選択したすべての形式で出力される。
IndentHtmlデバッグ用のプロパティ。通常はfalseで問題なし。
KeepLogFiletrueならビルド成功後もlogファイルを削除しない。falseなら削除する。
PlugInConfigurationsビルドプロセスプラグインを設定。
UserDefinedPropertiesMSBuildのプロジェクトプロパティを追加する。
Help File
ContentPlacement???(調べたんですが分かりませんでした...)
CopyrightHref"コピーライト"リンクのリンク先URL。
CopyrightText"コピーライト"リンクに表示される文字列。
FeedbackEMailAddress"フィードバックの送信"リンクのE-Mailアドレス。
FeedbackEMailLinkText"フィードバックの送信"リンクに表示される文字列。
FooterTextドキュメントの各ページに表示するフッター文字列。
HeaderTextドキュメントの各ページに表示するフッター文字列。
HelpTitleHelpFileのTitleを設定。
HtmlHelpNameコンパイルされたHtmlヘルプファイルにつけられる名前。
HtmlHelp1で出力すると[名前].chmでヘルプファイルが作成される。
LanguageHelpFileBuilderによって挿入される文字列の言語を設定。
NamingMethodHelp-htmlフォルダに作成されるファイル等の命名方法を設定。
Preliminarytrueならページヘッダーに仮のドキュメントであるという警告を出す。
Languageが日本語の場合、"[これは仮のドキュメントであり、予告なく変更されます。]"が表示される。
PresentationStyle生成されるページの外観を設定。
RootNamespaceContainertrueならルート名前空間のページが生成される。
RootNamespaceTitleルート名前空間の名前を設定。
SdkLinkTargetMSDNのLinkをクリックしたときのリンク先の開き方を設定。
HTML Help1、MS Help 2、MS Help Viewerのプロパティ-SDKLinkTypeがMsdnの場合のみ、このプロパティは有効となる。
SyntaxFiltersSyntax欄に表示するプログラミング言語を設定。
そのほかの重要なプロパティ
OutputPath生成されたHelpFileの出力先パスを設定。
Show Missing Tagsの各プロパティドキュメントコメントでタグが抜けている場合、抜けているタグのドキュメント出力位置にデフォルトのメッセージを表示するかどうかを設定。
Visibilityの各プロパティドキュメント化する対象を設定。

デフォルトの設定でもいいのですが、HelpFileFormatLanguagePresentationStyleあたりは
ちゃんと設定した方がいいと思います。

設定が完了したら、いよいよドキュメントを作成します。
メニューの[Documentation]-[Build Project]でビルドを実行します。
Build Outputウィンドウに"Build completed successfully at..."が出力されれば作成成功です。

Project PropertiesのOutputPathで設定した場所にMSDNチックなドキュメントが
作成されているのが確認できると思います。

今回はとりあえずここまで。
次回のエントリーでは、実際のドキュメントコメントとドキュメントの対応について書こうと思います。