C++で構文解析 Boost.Spirit.Qi #3
今回はQiの使いやすさを向上するものを紹介します。
構造体をtuple
として扱えるようにする
パーサに>>
演算子を適用すると、属性がtuple<A, B>
になることは前回説明しました。でもtuple
だと後々ゴチャゴチャしすぎるので、
struct Result {
A a;
B b;
};
みたいなのに突っ込めたらとても分かりやすくなってGoodです。Boost.Fusionを使うと出来ます。
// 構造体定義(グローバルでなくてもOK) struct Result { int x; double y; }; // Fusionのマクロ適用(グローバルスコープで行うこと) BOOST_FUSION_ADAPT_STRUCT(Result, x, y)
BOOST_FUSION_ADAPT_STRUCT
マクロには、最初に構造体、それ以降にメンバ名を指定します。これを使うと、Qiが構造体をtuple
とみなして扱ってくれます(正確には「構造体をFusionシーケンスに適応させる」が正しい。詳しく知りたい人はググってください)。
std::string str("12345, 67.89"); auto itr = str.begin(), end = str.end(); Result result; // 結果の格納先 bool success = qi::phrase_parse(itr, end, qi::int_ >> qi::lit(",") >> qi::double_, qi::space, result); if (success && itr == end) { std::cout << result.x << std::endl; // 構造体メンバでアクセスできる std::cout << result.y << std::endl; }
こんな風に結果の変数にアクセスできます。メンバには当然名前がついているのでパースした後の処理がとてもやりやすくなります。
余談: 本当は、属性tuple
はFusionコンテナであるboost::fusion::vector
のことです。前回std::tuple
使用時にインクルードしたboost/fusion/include/std_tuple.hpp
ファイルは、Fusionシーケンスに適応させるためのヘッダファイルとなっています。
C++で構文解析 Boost.Spirit.Qi #2
今回から、詳しい文法の定義方法を紹介します。
EBNF記法
コンピュータ言語の定義に使われるBNF(Backus-Naur form)を拡張したEBNF(Extended BNF)に近い書き方で文法を定義できるのがQiの特徴です。
例えば、EBNFで自然数(先頭が'0'でない1以上の数字列)を定義すると、
digit excluding zero ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" digit ::= "0" | digit excluding zero natural number ::= digit excluding zero, digit*
といった感じになります(詳しくはググってください)。Qiではほぼこのまま(演算子が違ったりはするけど)文法を定義できます。おかげでC++書いている心地がしません。
続きを読むC++で構文解析 Boost.Spirit.Qi #1
Boost.Spirit.Qi(以下Qi)はC++用の構文解析ライブラリです。テンプレートや演算子オーバーロードを最大限に駆使しており、C++内で文法定義を完結させることができます。その分コンパイルがとても遅いです。
準備
QiはBoostの中に含まれています。Qiはビルド不要なライブラリなのでVisual Studioを使用している場合はNuGet経由でインストールすることができます。
ヘッダファイルをインクルードして準備は完了です。namespace
などはお好みで。
#include <boost/spirit/include/qi.hpp> namespace qi = boost::spirit::qi;続きを読む
INotifyPropertyChangedのよくある実装
大体こんな感じで安定してきたので書き残し。
public abstract class BindableBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (Equals(storage, value)) return false; storage = value; RaisePropertyChanged(propertyName); return true; } protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
以前まではExpression Tree使ってリファクタリングが効くようにしていたRaisePropertyChangedのオーバーロードがあったりもしたけど、nameof演算子が使えるようになってからは全く使わなくなったので省略。
実際の使い方は以下の通り。
public class WindowViewModel : BindableBase { private string _labelText; public string LabelText { get { return _labelText; } set { SetProperty(ref _labelText, value); } } }