スペアりぶ

びぼうろく

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); }
    }
}