ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

c – 在Spirit中构建自定义表达式树:Qi(不使用Utree或Boost :: Variant)

2019-09-29 02:18:02  阅读:182  来源: 互联网

标签:boost-spirit-qi c parsing boost-spirit expression-trees


首先,如果使用Boost Variant或Utree更容易,那么我将与他们达成和解,我将尝试在另一个主题中解决我们的问题.但是,我非常希望能够像下面一样构建一棵树.

背景,如果你想直接讨论这个问题,请忽略:我希望能够构建一个解析像

"({a} == 0) && ({b} > 5)"

或标准的数学表达式

"(2 * a) + b"

然后我将在评估树之前定义a和b是什么,如下所示:

a = 10;
double val = myExpression->Evaluate();

我的问题来自于当我尝试构建try以将字符串解析为我的表达式树时.我正在使用一个抽象类“Expression”,然后导出“变量”,“常量”和“二进制”表达式(它也会做一元,但它不应该影响我的问题.我一直有使用我的规则添加到树的问题所以我显然做错了什么.我很难绕着属性缠头.

我的树如下(Tree.h):

class BinaryExpression;
typedef double (*func)(double, double);

class Expression
{
public:
    virtual double Evaluate() = 0;
};

class BinaryExpression : public Expression
{
private:
    Expression* lhs;
    Expression* rhs;
    func method;

    double Evaluate();

public:
    BinaryExpression(void);
    BinaryExpression(char op, Expression* lhs, Expression* rhs);
    BinaryExpression(char op);
    void operator()(Expression* lhs, Expression* rhs);
};

class ConstantExpression : public Expression
{
private:
    double value;
public:
    ConstantExpression(void);
    ConstantExpression(char op);
    ConstantExpression(double val);

    double Evaluate();
};

// Require as many types as there are fields in expression?
static double a;
static double b;
class VariableExpression : public Expression
{
private:
    char op;

public:
    VariableExpression(char op);

    double Evaluate();
};

BOOST_FUSION_ADAPT_STRUCT(
    BinaryExpression,
    (Expression*, lhs)
    (Expression*, rhs)
    (func, method)
)

BOOST_FUSION_ADAPT_STRUCT(
    VariableExpression,
    (char, op)
)

BOOST_FUSION_ADAPT_STRUCT(
    ConstantExpression,
    (double, op)
)

Tree.cpp

typedef double (*func)(double, double);

/////////////////////////////////////////////////////////////////////////////
// BINARY EXPRESSION
////////////////////////////////////////////////////////////////////////////

BinaryExpression::BinaryExpression(void) {}

BinaryExpression::BinaryExpression(char op, Expression* lhs, Expression* rhs)
{
    this->lhs = lhs;
    this->rhs = rhs;

    // Example, methods are held in another header
    if (op == '+')
        method = Add;
    else if (op == '-')
        method = Subtract;

}

double BinaryExpression::Evaluate()
{
    return method(lhs->Evaluate(), rhs->Evaluate());
}

BinaryExpression::BinaryExpression(char op)
{
    if (op == '+')
        method = Add;
    else if (op == '-')
        method = Subtract;
}

void BinaryExpression::operator()(Expression* lhs, Expression* rhs)
{
    this->lhs = lhs;
    this->rhs = rhs;
}

/////////////////////////////////////////////////////////////////////////////
// CONSTANT EXPRESSION
////////////////////////////////////////////////////////////////////////////

ConstantExpression::ConstantExpression() {}

ConstantExpression::ConstantExpression(char op)
{
    this->value = op - 48;
}
ConstantExpression::ConstantExpression(double val)
{
    value = val;
}

double ConstantExpression::Evaluate()
{
    return value;
}

/////////////////////////////////////////////////////////////////////////////
// VARIABLE EXPRESSION
////////////////////////////////////////////////////////////////////////////

VariableExpression::VariableExpression(char op)
{
    this->op = op;
}

double VariableExpression::Evaluate()
{
    // a and b are defined in the header, and are used to fill in the variables we     want to evaluate
    if (op == 'a')
        return a;
    if (op == 'b')
        return b;
    return 0;
}

现在,如果我手动构建树,它一切正常,所以我不认为它的结构方式存在问题.

这是Grammar.h(很多评论来自我尝试各种各样的东西,我可以删除它们,但我可能值得展示我尝试过的东西/我想去的地方)

#include "Tree.h"

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_function.hpp>

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

qi::_1_type _1;
qi::_2_type _2;

// Pass functions to boost
boost::phoenix::function<BinaryExpression> plus = BinaryExpression('+');
boost::phoenix::function<BinaryExpression> minus = BinaryExpression('-');

template <typename Iterator>
struct ExpressionParser : qi::grammar<Iterator, BinaryExpression(), ascii::space_type>
{
    ExpressionParser() : ExpressionParser::base_type(expression)
    {
        qi::_3_type _3;
        qi::_4_type _4;

        qi::char_type char_;
        qi::uint_type uint_;
        qi::_val_type _val;
        qi::raw_type raw;
        qi::lexeme_type lexeme;
        qi::alpha_type alpha;
        qi::alnum_type alnum;
        qi::bool_type bool_;
        qi::double_type double_;


        expression = //?
            additive_expr                       [_val = _1]
            ;

        //equality_expr = 
        //      relational_expr >> 
        //      *(lit("==") > relational_expr)      [/*Semantice action to add to tree*/]
        //      ;

        additive_expr =
            primary_expr >>
            ( '+' > primary_expr)               [plus(_val, _1)]   
            | ( '-' > primary_expr)             [minus(_val, _1)]
            ;
        // Also tried "_val = plus(_1, _2)"

        primary_expr =
            constant                                [_val = _1]
            | variable                          [_val = _1]
            //| '(' > expression > ')'          [_val = _1]
            ;

        string %=
            '{' >> *(char_ - '}') >> '}'
            ;

        // Returns ConstantExpression
        constant =
            double_                                 [_val = _1];

        // Returns VariableExpression
        variable =
            char_                                   [_val = _1]
            ;
    }

    // constant expression = double
    // variable expression = string
    qi::rule<Iterator, BinaryExpression(), ascii::space_type>
        expression;

    qi::rule<Iterator, BinaryExpression(), ascii::space_type>
        // eventually will deal with all these rules
        equality_expr,
        relational_expr,        
        logical_expr,
        additive_expr,
        multiplicative_expr,
        primary_expr
            ;

    qi::rule<Iterator, ConstantExpression(), ascii::space_type>
        constant
        ;

    qi::rule<Iterator, VariableExpression(), ascii::space_type>
        variable
        ;

    qi::rule<Iterator, std::string(), ascii::space_type>
        string
        ;
};

所以这是一个真正被黑客攻击,但希望它会显示我想要实现的目标.任何建议或提示将非常感激.有没有一个例子,有人建立了这样的树而不使用变体或utree.

也很抱歉,如果我违反惯例,并为我的格式,我试图使其尽可能可读.

解决方法:

我不清楚你对(递归)变体的抱怨是什么,但是这里有一个变化,你希望使用动态分配的节点来使用’老式’树构建:

> http://liveworkspace.org/code/3VS77n$0

我有目的地回避了你的语法中运算符优先级的问题,因为

>你的语法不完整
>我不知道所需的语义(毕竟,你似乎也支持布尔评估,但我不知道如何)
>您可以在其他答案中了解这些:

> Boolean expression (grammar) parser in c++
> Boost::Spirit Expression Parser
> Compilation error with a boost::spirit parser显示了另一种方法

请注意我

>使用shared_ptr删除无处不在的内存泄漏(如果没有TR1库,可以使用Boost)
>我删除了错误的重用特定BinaryExpression实例作为凤凰懒惰的演员.相反,我现在做了一个当地的二元演员.
>注意现在如何支持操作符链(1 2 5 6-10):

additive_expr =
    primary_expr                         [ _val = _1 ]
    >> *(char_("-+*/") >> primary_expr)  [ _val = makebinary(_1, _val, _2)]
    ;

>我添加了{var},/,*和(expr)支持
>为显示添加了序列化(打印虚拟方法,运算符<<)(为了便于显示,BinaryExpression现在存储运算符而不是结果方法)
>因此现在你可以使用BOOST_SPIRIT_DEBUG(取消注释第一行)
>我已将Expression重命名为AbstractExpression(并使de构造函数受保护)
>我已将PrimaryExpression重命名为Expression(现在这是您的主要表达式数据类型)
>我将展示如何在静态地图中存储简单变量

>一定要看看qi ::符号和
>例如How to add qi::symbols in grammar<Iterator,double()>?

>使用更少的融合结构适应(仅适用于变量)
>使用模板化的构造函数技巧,可以很容易地从不同的解析类型构造表达式:

struct Expression : AbstractExpression {
    template <typename E>
    Expression(E const& e) : _e(make_from(e)) { } // cloning the expression
    // ...
};

足以有效支持例如:

primary_expr =
      ( '(' > expression > ')' )         [ _val = _1 ]
    | constant                           [ _val = _1 ]
    | variable                           [ _val = _1 ]
    ;

>为了好玩,还包括了一些测试用例:

Input:                3*8 + 6
Expression:           Expression(BinaryExpression(BinaryExpression(ConstantExpression(3) * ConstantExpression(8)) + ConstantExpression(6)))
Parse success:        true
Remaining unparsed:  ''
(a, b):               0, 0
Evaluation result:    30
----------------------------------------
Input:                3*(8+6)
Expression:           Expression(BinaryExpression(ConstantExpression(3) * BinaryExpression(ConstantExpression(8) + ConstantExpression(6))))
Parse success:        true
Remaining unparsed:  ''
(a, b):               0, 0
Evaluation result:    42
----------------------------------------
Input:                0x1b
Expression:           Expression(ConstantExpression(27))
Parse success:        true
Remaining unparsed:  ''
(a, b):               0, 0
Evaluation result:    27
----------------------------------------
Input:                1/3
Expression:           Expression(BinaryExpression(ConstantExpression(1) / ConstantExpression(3)))
Parse success:        true
Remaining unparsed:  ''
(a, b):               0, 0
Evaluation result:    0.333333
----------------------------------------
Input:                .3333 * 8e12
Expression:           Expression(BinaryExpression(ConstantExpression(0.3333) * ConstantExpression(8e+12)))
Parse success:        true
Remaining unparsed:  ''
(a, b):               0, 0
Evaluation result:    2.6664e+12
----------------------------------------
Input:                (2 * a) + b
Expression:           Expression(BinaryExpression(BinaryExpression(ConstantExpression(2) * VariableExpression('a')) + VariableExpression('b')))
Parse success:        true
Remaining unparsed:  ''
(a, b):               10, 7
Evaluation result:    27
----------------------------------------
Input:                (2 * a) + b
Expression:           Expression(BinaryExpression(BinaryExpression(ConstantExpression(2) * VariableExpression('a')) + VariableExpression('b')))
Parse success:        true
Remaining unparsed:  ''
(a, b):               -10, 800
Evaluation result:    780
----------------------------------------
Input:                (2 * {a}) + b
Expression:           Expression(BinaryExpression(BinaryExpression(ConstantExpression(2) * VariableExpression('a')) + VariableExpression('b')))
Parse success:        true
Remaining unparsed:  ''
(a, b):               -10, 800
Evaluation result:    780
----------------------------------------
Input:                {names with spaces}
Expression:           Expression(VariableExpression('names with spaces'))
Parse success:        true
Remaining unparsed:  ''
(a, b):               0, 0
Evaluation result:    0
----------------------------------------

Full Code

// #define BOOST_SPIRIT_DEBUG
// #define BOOST_RESULT_OF_USE_DECLTYPE
// #define BOOST_SPIRIT_USE_PHOENIX_V3

#include <cassert>
#include <memory>
#include <iostream>
#include <map>

struct AbstractExpression;
typedef std::shared_ptr<AbstractExpression> Ptr;

struct AbstractExpression {
    virtual ~AbstractExpression() {}
    virtual double Evaluate() const = 0;
    virtual std::ostream& Print(std::ostream& os) const = 0;

    friend std::ostream& operator<<(std::ostream& os, AbstractExpression const& e)
        { return e.Print(os); }

    protected: AbstractExpression() {}
};

template <typename Expr> // general purpose, static Expression cloner
    static Ptr make_from(Expr const& t) { return std::make_shared<Expr>(t); }

struct BinaryExpression : AbstractExpression 
{
    BinaryExpression() {}

    template<typename L, typename R>
    BinaryExpression(char op, L const& l, R const& r) 
        : _op(op), _lhs(make_from(l)), _rhs(make_from(r)) 
    {}

    double Evaluate() const {
        func f = Method(_op);
        assert(f && _lhs && _rhs);
        return f(_lhs->Evaluate(), _rhs->Evaluate());
    }

  private:
    char _op;
    Ptr _lhs, _rhs;

    typedef double(*func)(double, double);

    static double Add(double a, double b)      { return a+b; }
    static double Subtract(double a, double b) { return a-b; }
    static double Multuply(double a, double b) { return a*b; }
    static double Divide(double a, double b)   { return a/b; }

    static BinaryExpression::func Method(char op)
    {
        switch(op) {
            case '+': return Add;
            case '-': return Subtract;
            case '*': return Multuply;
            case '/': return Divide;
            default:  return nullptr;
        }
    }
    std::ostream& Print(std::ostream& os) const
        { return os << "BinaryExpression(" << *_lhs << " " << _op << " " << *_rhs << ")"; }
};

struct ConstantExpression : AbstractExpression {
    double value;
    ConstantExpression(double v = 0) : value(v) {}

    double Evaluate() const { return value; }

    virtual std::ostream& Print(std::ostream& os) const
        { return os << "ConstantExpression(" << value << ")"; }
};

struct VariableExpression : AbstractExpression {
    std::string _name;

    static double& get(std::string const& name) {
        static std::map<std::string, double> _symbols;
        return _symbols[name];
        /*switch(name) {
         *    case 'a': static double a; return a;
         *    case 'b': static double b; return b;
         *    default:  throw "undefined variable";
         *}
         */
    }

    double Evaluate() const { return get(_name); }

    virtual std::ostream& Print(std::ostream& os) const
        { return os << "VariableExpression('" << _name << "')"; }
};

struct Expression : AbstractExpression
{
    Expression() { }

    template <typename E>
    Expression(E const& e) : _e(make_from(e)) { } // cloning the expression

    double Evaluate() const { assert(_e); return _e->Evaluate(); }

    // special purpose overload to avoid unnecessary wrapping
    friend Ptr make_from(Expression const& t) { return t._e; }
  private:
    Ptr _e;
    virtual std::ostream& Print(std::ostream& os) const
        { return os << "Expression(" << *_e << ")"; }
};

//Tree.cpp

/////////////////////////////////////////////////////////////////////////////
// BINARY EXPRESSION
////////////////////////////////////////////////////////////////////////////

//#include "Tree.h"
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/adapted.hpp>

BOOST_FUSION_ADAPT_STRUCT(VariableExpression, (std::string, _name))

namespace qi    = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phx   = boost::phoenix;

// Pass functions to boost
template <typename Iterator>
struct ExpressionParser : qi::grammar<Iterator, Expression(), ascii::space_type> 
{
    struct MakeBinaryExpression {
        template<typename,typename,typename> struct result { typedef BinaryExpression type; };

        template<typename C, typename L, typename R>
            BinaryExpression operator()(C op, L const& lhs, R const& rhs) const 
            { return BinaryExpression(op, lhs, rhs); }
    };

    phx::function<MakeBinaryExpression> makebinary;

    ExpressionParser() : ExpressionParser::base_type(expression) 
    {
        using namespace qi;
        expression =
            additive_expr                        [ _val = _1]
            ;

        additive_expr =
            primary_expr                         [ _val = _1 ]
            >> *(char_("-+*/") >> primary_expr)  [ _val = makebinary(_1, _val, _2)]
            ;

        primary_expr =
              ( '(' > expression > ')' )         [ _val = _1 ]
            | constant                           [ _val = _1 ]
            | variable                           [ _val = _1 ]
            ;

        constant = lexeme ["0x" >> hex] | double_ | int_;
        string   = '{' >> lexeme [ *~char_("}") ] > '}';
        variable = string | as_string [ alpha ];

        BOOST_SPIRIT_DEBUG_NODE(expression);
        BOOST_SPIRIT_DEBUG_NODE(additive_expr);

        BOOST_SPIRIT_DEBUG_NODE(primary_expr);
        BOOST_SPIRIT_DEBUG_NODE(constant);
        BOOST_SPIRIT_DEBUG_NODE(variable);
        BOOST_SPIRIT_DEBUG_NODE(string);
    }

    qi::rule<Iterator, Expression()        , ascii::space_type> expression;
    qi::rule<Iterator, Expression()        , ascii::space_type> additive_expr;

    qi::rule<Iterator, Expression()        , ascii::space_type> primary_expr;
    qi::rule<Iterator, ConstantExpression(), ascii::space_type> constant;
    qi::rule<Iterator, VariableExpression(), ascii::space_type> variable;
    qi::rule<Iterator, std::string()       , ascii::space_type> string;
};

void test(const std::string input, double a=0, double b=0)
{
    typedef std::string::const_iterator It;
    ExpressionParser<It> p;

    Expression e;
    It f(input.begin()), l(input.end());
    bool ok = qi::phrase_parse(f,l,p,ascii::space,e);

    std::cout << "Input:                "  << input            << "\n";
    std::cout << "Expression:           "  << e                << "\n";
    std::cout << "Parse success:        "  << std::boolalpha   << ok << "\n";
    std::cout << "Remaining unparsed:  '"  << std::string(f,l) << "'\n";

    std::cout << "(a, b):               "  << a << ", " << b   << "\n";

    VariableExpression::get("a") = a;
    VariableExpression::get("b") = b;
    std::cout << "Evaluation result:    "  << e.Evaluate()     << "\n";
    std::cout << "----------------------------------------\n";
}

int main() 
{
    test("3*8 + 6"); 
    test("3*(8+6)"); 
    test("0x1b"); 
    test("1/3"); 
    test(".3333 * 8e12");
    test("(2 * a) + b",    10,   7);
    test("(2 * a) + b",   -10, 800);
    test("(2 * {a}) + b", -10, 800);
    test("{names with spaces}");
}

标签:boost-spirit-qi,c,parsing,boost-spirit,expression-trees
来源: https://codeday.me/bug/20190929/1830036.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有