メインコンテンツに移動

Parse

Boost.SpiritでParseを実現

基本的には文字列をboost::spirit::qi::parse()関数に食わせると, 文法通りに解釈して結果が得られる,というものだ.あ?ここ,いいかもな.いやいや,世の中にはこんな奇特な人もいる.他力本願が我々の本懐であり,できることならプログラミングなんてしたくないので,とりあえずサンプルから使えそうなクラスができたら良いであろう

//  ドッグ作 https://rhysd.hatenablog.com/entry/2013/12/24/003122 「はやくプログラムになりたい」より引用

#ifndef calculator_h
#define calculator_h

#include 
#include 
#include 

#define BOOST_RESULT_OF_USE_DECLTYPE
#define BOOST_SPIRIT_USE_PHOENIX_V3

#include 
#include 
#include 
#include 
#include 
#include 

namespace calculator
{
enum struct operators {
    add, sub, mul, div, log, exp
};
template struct binary_operator;
template struct unary_operator;
constexpr static char const* to_string(operators op)
{
    switch(op)
    {
        case operators::add: return "+";
        case operators::sub: return "-";
        case operators::mul: return "*";
        case operators::div: return "/";
        case operators::log: return "log";
        case operators::exp: return "exp";
        default: return nullptr;
    }
}
//boost::variant はunionみたいな共用体である.
//  unionと違って, typeはユーザー定義型でも良い.
//boost::variant E; と宣言した変数Eは,doubleであるか, intであるか, charである.
//  E.which() で何番目の型であるのかわかる. E.type()==typeid(double) とやると, doubleであるのか判定できる
using expression=boost::variant>
,boost::recursive_wrapper>
,boost::recursive_wrapper>
,boost::recursive_wrapper>
,boost::recursive_wrapper>
,boost::recursive_wrapper>
>;
//boost::recursive_wrapperとは, 不思議な力で再帰的な型定義になっても問題なく型が定義できるようになるもの,だそう
//  なんのこっちゃ. recursive_wrapper can hold types T whose member data leads to a circular dependency
//      (e.g., a data member of T has a data member of type T)
//  ということらしいが.
//それを利用して,型定義の再帰で木を,多層型で各ノードを表現して式木をつくる
//  どういうこと? expressionは, doubleの値を取るときには,それは数値であるのでこれ以上展開する必要はない
//      それ以外の場合, binary_operatorか, unary_operatorのいずれかである.
//          binary_operatorには, メンバーとして left, right がある. これがまたexpressionである.
//          unary_operatorには, メンバーとして child がある.
//  というわけで, expression は, 式のツリーを構成するのだ. ←  自作のvar.hppの Op クラスと同じものだ! Boostに既>
//      expression--binary.left--binary.left--unary.child--double
//                        .right       .right
//                           |            |
//                           |            +---double
//                           |
//                           +---unary.child--binary.left--double
//                                                  .right
//                                                     |
//                                                     +---double       こんな感じで,式を表現するわけよ.
template
class binary_operator
{
public:
    expression left;
    expression right;
    binary_operator(expression const& lhs, expression const& rhs):left(lhs),right(rhs){}
};

template
class unary_operator
{
public:
    expression child;
    unary_operator(expression const& rhs):child(rhs){}
};

//ここまでで基本のオペレータが定義できたことになる.
//boost::variantの続き
//  variantの値を評価したいと思うだろう. もちろんtype()とかでいちいちコードしても良いが, なにか統一的な型に
//  変換するのも一手である. そのときには, boost::apply_visitor()を利用する. 多数の定義があるが,ここでは
//      boost::apply_visitor(評価インスタンス,variant変数)
//  を用いる. すると, 評価インスタンス(variant変数) を実行する. 評価インスタンスは何型になるのかを知らなければな
//  boost::static_visitor を継承する(するとResultTypeなるメンバーが最終型名を記憶している,らしい)
//というわけで, 評価者を定義する. こいつは全てのexpressionをdouble値に変更しようとする.
//  評価者は, ぶち込まれたVariantの種類に応じて()オペレータが定義してある.
//      例: calculator(binary_operator op)の場合
//              op.left と op.right メンバーがあるので,とりま,そいつらをapply_visitor()する.
//              double数値になって帰ってきたら, + 演算を行い, return する.
//  他の演算も同じである. これによって, てっぺんのexporessionを食わすと,枝をたぐりながら評価していく ←  お手製 
class calculator : public boost::static_visitor
{
public:
    double operator()(double const constant) const
    {
        return constant;
    }
    double operator()(binary_operator const& op) const
    {
        return boost::apply_visitor( calculator(), op.left )
        + boost::apply_visitor( calculator(), op.right );
    }
    double operator()(binary_operator const& op) const
    {
        return boost::apply_visitor( calculator(), op.left )
        - boost::apply_visitor( calculator(), op.right );
    }
    double operator()(binary_operator const& op) const
    {
        return boost::apply_visitor( calculator(), op.left )
        * boost::apply_visitor( calculator(), op.right );
    }
    double operator()(binary_operator const& op) const
    {
        return boost::apply_visitor( calculator(), op.left )
        / boost::apply_visitor( calculator(), op.right );
    }
    double operator()(unary_operator const& op) const
    {
        return std::log( boost::apply_visitor( calculator(), op.child ) );
    }
    double operator()(unary_operator const& op) const
    {
        return std::exp( boost::apply_visitor( calculator(), op.child ) );
    }
};
//mainで入力数式の値を開始する人
inline double calc( expression const& expr )
{
    return boost::apply_visitor(calculator(), expr);
}

//  入力数式を表示する人. 計算と同じようにツリーを手繰りながら文字列化する
struct stringizer : public boost::static_visitor{

    std::string operator()(double const constant) const
    {
        return std::to_string(constant);
    }

    template
    std::string operator()(binary_operator const& op) const
    {
        return '(' + boost::apply_visitor(stringizer(), op.left)
        + to_string(BinaryOp)
        + boost::apply_visitor(stringizer(), op.right) + ')';
    }

    template
    std::string operator()(unary_operator const& op) const
    {
        return to_string(UnaryOp) + ('(' + boost::apply_visitor(stringizer(), op.child) + ')');
    }

};
//mainで入力数式の表示を開始する人
inline std::string stringize(expression const& expr)
{
    return boost::apply_visitor(stringizer(), expr);
}

//ここから上は,expressionが与えられたときに, その数式を表示したり, 値を計算するプログラムである.
//ここから下は, expressionをParserを使って作成するのではないかな

//Boost.Phoenixとはなにか. これは, ラムダ式をC++で実現しようという試みである.
//      boost::lambda   ラムダ式の実現をテストした最初のライブラリ
//      std::function   C++標準がラムダ式を実装した
//      boost::phoenix  それらの成果を受けて, さらに前進するライブラリ
//という感じに見えるのだが. boost::phoenixの特徴は,関数の返り値がラムダ式があり,ということらしい.
//例文
//      auto my_std_lambda = [](int i){return i%2==1;}; //oddならtrueって感じ
//      auto my_phoenix = boost::phoenix::placeholders::arg1 % 2 == 1;
//なるほど. ちょいと違うね.
//
//で,これらは何をしているのか.
//      標準lambda式 [](lhs,rhs){...}が, binary_operator(lhs,rhs)を返却する.
//      例えば +演算に対応するmake_binary_operatorは, 多分expressionであるlhs,rhsを引数に
//      与えて呼び出すと, +expressionを返却するラムダ式を戻す. のだが, それをbindして,
//      lhsに boost::spirit::_valを, rhs に boost::spirit::_1 を与えるものである.
template
auto make_binary_operator()
{
    return boost::phoenix::bind([](auto const& lhs, auto const& rhs){
        return binary_operator(lhs, rhs);
    }, boost::spirit::_val, boost::spirit::_1 );
}
template
auto make_unary_operator()
{
    return boost::phoenix::bind([](auto const& e){
        return unary_operator(e);
    }, boost::spirit::_1);
}

namespace parser_impl
{
//で,ようやく, パーサーを定義するのである.
//using namespace boost::spirit;
//mainで expr_grammer と宣言しているので, std::string::const_iteratorとでも言いたいの>
//なんのためにtemplateになっているのかは不明だ.
//
//いよいよ boost::spirit::qi::grammar を理解する.
//  boost::spirit::qi::grammar とは, 複数の boost::spirit::qi::rule の集合である.
template
class expr_grammer:public boost::spirit::qi::grammar
{
public:
    //boost::spirit::qi::rule を先に理解しよう. これは
    //      rule入力文字イテレータ,値を返すところのシグネチャ,スキッパ>
    //      rule入力文字イテレータ,値を返すところのシグネチャ,ローカル変数,スキッパ>
    //  シグネチャ,ってよくわからないんだけど?
    //  スキッパーは, コメントをすっ飛ばすためのものである.
    //いずれにせよ, ここでは expr, term, fctr の3つのルールを定義している.
    //ルールは単に規則を定めただけである. 実際の解釈は, boost::spirit::qi::phrase_parse などでおこなう.
    //そこでは,入力文字, ルール, および値を返すとこを指定してParseを実行する.
    boost::spirit::qi::rule expr, term, fctr;
    expr_grammer() : expr_grammer::base_type(expr)
    {
        //問題はこの, rule の書き方である.
        //数マッチ:
        //  boost::spirit::qi::int_                 「整数」と解釈する
        //  boost::spirit::qi::double_              「実数」と解釈する
        //  int_ >> double_                         「整数 実数」と解釈する
        //  *double_                                「たくさんの実数」
        //  double >> *( char_(',') >> double_ )    「実数,実数,....,実数」と解釈する
        //意味マッチ(semantic actions):
        //  Pがマッチした場合に, Fを行う場合, P[F] と表記する.
        //  expr = term[...] >> .... と書いてあるので, termがマッチする必要がある.
        //  term = fctr[...] >> .... と書いてあるので, fctrがマッチする必要がある.
        //  fctr = double_[...] | '(' >> expr[...] >> ')' |... と書いてある. なので, 数として解釈できるなら終了.
        //          ( 式 ) となっていれば exprから再トライ
        //  これで再帰的に式ツリーを構築する.
        //  --
        //  で, ...に書いてある謎の式は何だ? boost::phoenixとして解釈可能らしいぞ.
        //  一番簡単な
        //      boost::spirit::double_[boost::spirit::_val = boost::spirit::_1]
        //  では,
        //      1.お, これ, 浮動小数点として解釈可能じゃん.
        //      2.んだば,semanticsに書いてあるboost::spirit::_val = boost::spirit::_1を実行
        //          boost::spirit::_val とは phoenix::actor0>> _val が定義である. つまりこいつはラム>
        //          _val refers to the 'return' value of a rule. のように作れば良いらしい.
        //          で, _val()関数は, boost::spirit::_1 を返却するように作る.
        //          boost::spirit::_1, _2, ... refer to the attributes of the single
        //                                  components the lhs parser is composed of
        //          であるので, _val()関数は, その適用した浮動小数点数を返却する関数になる.
        //  複雑な
        //      ('+' >> term[boost::spirit::_val = make_binary_operator()])
        //  を考えよう. 例えば  12+25 という式の場合, 12の部分はfctrから帰ってきて
        //      term[boost::spirit::_val = boost::spirit::_1] >> ...
        //  で_val()関数によりその数値をゲット可能だ. そして+ルールに嵌まり, make_binary_operator()が呼び出
        //  するとだ. こいつは expression Pを返すのだが, P.leftは_val()になっているので・・・12になる. P.rightは
        //  +の後ろにいるやつ, 25である.
        //  げげえ,これでうまく行くのね. +-より先に*/が実行されてしまうのも都合が良く, ()が最強というのも良い.
        //  天才じゃね?このひと
        //
        expr = term[boost::spirit::_val = boost::spirit::_1]
            >> *( ('+' >> term[boost::spirit::_val = make_binary_operator()])
                | ('-' >> term[boost::spirit::_val = make_binary_operator()]) );
        term = fctr[boost::spirit::_val = boost::spirit::_1]
            >> *( ('*' >> fctr[boost::spirit::_val = make_binary_operator()])
                | ('/' >> fctr[boost::spirit::_val = make_binary_operator()]) );
        fctr = boost::spirit::double_[boost::spirit::_val = boost::spirit::_1]
            | '(' >> expr[boost::spirit::_val = boost::spirit::_1] >> ')'
            | "log(" >> expr[boost::spirit::_val = make_unary_operator()] >> ")"
            | "exp(" >> expr[boost::spirit::_val = make_unary_operator()] >> ")";
    }
};
} // namespace parser_impl

} // namespace calc
//int main()
//{
//  std::string buffer;
//  while(std::getline(std::cin, buffer)){
//      calculator::expression expr;
//      calculator::parser_impl::expr_grammer p;
//      if (boost::spirit::qi::phrase_parse(buffer.begin(), buffer.end(), p, boost::spirit::ascii::space, expr))
//      {
//          std::cout  calculator::stringize(expr)  " = "  calculator::calc(expr)  std::endl;
//      } else {
//          std::cerr  "Parse error!!\n";
//      }
//  }
//  return 0;
//}