【问题标题】:How to print the variables matched by the symbol table in Boost spirit parser?Boost Spirit解析器中如何打印符号表匹配的变量?
【发布时间】:2015-09-10 11:14:44
【问题描述】:

我是使用boost spirit的初学者

假设我有以下代码可以解析带有变量的简单算术表达式:

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/variant/recursive_variant.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix_function.hpp>
#include <boost/foreach.hpp>

#include <iostream>
#include <string>

namespace client {
    namespace ast
    {
        struct nil {};
        struct signed_;
        struct program;

        typedef boost::variant<
            nil
            , double
            , boost::recursive_wrapper<signed_>
            , boost::recursive_wrapper<program>
        >
        operand;

        struct signed_
        {
            char sign;
            operand operand_;
        };

        struct operation
        {
            char operator_;
            operand operand_;
        };

        struct program
        {
            operand first;
            std::list<operation> rest;
        };
    }
}

BOOST_FUSION_ADAPT_STRUCT(
    client::ast::signed_,
    (char, sign)
    (client::ast::operand, operand_)
    )

    BOOST_FUSION_ADAPT_STRUCT(
    client::ast::operation,
    (char, operator_)
    (client::ast::operand, operand_)
    )

    BOOST_FUSION_ADAPT_STRUCT(
    client::ast::program,
    (client::ast::operand, first)
    (std::list<client::ast::operation>, rest)
    )

namespace client {
    namespace ast
    {
        struct eval
        {
            typedef double result_type;

            double operator()(nil) const { BOOST_ASSERT(0); return 0; }
            double operator()(double n) const { return n; }

            double operator()(operation const& x, double lhs) const
            {
                double rhs = boost::apply_visitor(*this, x.operand_);
                switch (x.operator_)
                {
                case '+': return lhs + rhs;
                case '-': return lhs - rhs;
                case '*': return lhs * rhs;
                case '/': return lhs / rhs;
                }
                BOOST_ASSERT(0);
                return 0;
            }

            double operator()(signed_ const& x) const
            {
                double rhs = boost::apply_visitor(*this, x.operand_);
                switch (x.sign)
                {
                case '-': return -rhs;
                case '+': return +rhs;
                }
                BOOST_ASSERT(0);
                return 0;
            }

            double operator()(program const& x) const
            {
                double state = boost::apply_visitor(*this, x.first);
                BOOST_FOREACH(operation const& oper, x.rest)
                {
                    state = (*this)(oper, state);
                }
                return state;
            }
        };
    }
}

namespace client
{
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    using boost::phoenix::function;

    template <typename Iterator>
    struct calculator : qi::grammar<Iterator, ast::program(), ascii::space_type>
    {
        calculator() : calculator::base_type(expression)
        {
            qi::char_type char_;
            qi::double_type doubleParser_;

            symboleTable.add("var1", 2);
            symboleTable.add("var2", 15);
            symboleTable.add("var4", 5);
            symboleTable.add("var", 5);
            symboleTable.add("x", 5);

            expression =
                term
                >> *((char_('+') > term)
                | (char_('-') > term)
                )
                ;

            term =
                factor
                >> *((char_('*') > factor)
                | (char_('/') > factor)
                )
                ;

            factor =
                doubleParser_
                | symbolTable
                | '(' > expression > ')'
                | (char_('-') > factor)
                | (char_('+') > factor)
                ;
        }
        qi::symbols<char, double> symbolTable;
        qi::rule<Iterator, ast::program(), ascii::space_type> expression;
        qi::rule<Iterator, ast::program(), ascii::space_type> term;
        qi::rule<Iterator, ast::operand(), ascii::space_type> factor;
    };
}

/////////////////////////////////////////////////////////////////////////////
//  Main program
/////////////////////////////////////////////////////////////////////////////
int
main()
{
    std::cout << "/////////////////////////////////////////////////////////\n\n";
    std::cout << "Expression parser...\n\n";
    std::cout << "/////////////////////////////////////////////////////////\n\n";
    std::cout << "Type an expression...or [q or Q] to quit\n\n";

    typedef std::string::const_iterator iterator_type;
    typedef client::calculator<iterator_type> calculator;
    typedef client::ast::program ast_program;
    typedef client::ast::eval ast_eval;

    std::string str;
    while (std::getline(std::cin, str))
    {
        if (str.empty() || str[0] == 'q' || str[0] == 'Q')
            break;

        calculator calc;        // Our grammar
        ast_program program;    // Our program (AST)
        ast_eval eval;          // Evaluates the program

        std::string::const_iterator iter = str.begin();
        std::string::const_iterator end = str.end();

        boost::spirit::ascii::space_type space;
        bool r = phrase_parse(iter, end, calc, space, program);

        if (r && iter == end)
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing succeeded\n";
            std::cout << "\nResult: " << eval(program) << std::endl;
            std::cout << "-------------------------\n";
        }
        else
        {
            std::string rest(iter, end);
            std::cout << "-------------------------\n";
            std::cout << "Parsing failed\n";
            std::cout << "-------------------------\n";
        }
    }

    std::cout << "Bye... :-) \n\n";
    return 0;
}

我想在符号表(在语法中声明)匹配时打印变量(而不是它们的值)。

所以例如当输入是

var* 2 - 3 +x*var2 - 2

输出应该是:

var
x
var2

有什么帮助吗?

【问题讨论】:

    标签: c++ boost boost-spirit


    【解决方案1】:

    使用的 AST 不存储引用的原始变量。

    因此解析信息后不再可用(AST 只包含值节点而不是原始引用)。

    有两种方法:

    • 丰富 AST,以便您仅在评估时解析变量(保留变量引用名称)

      更新我添加了another answer,它实际上实现了这个更精细的方法

    • 让解析器在解析期间“带外”收集变量引用。

    后者需要的努力要小得多(如果你知道精神 + 凤凰的把戏)。所以让我们证明:

            factor =
                doubleParser_
                | variable
                | '(' > expression > ')'
                | (char_('-') > factor)
                | (char_('+') > factor)
                ;
    

    这里我用新规则替换了symbolTablevariable

        qi::rule<Iterator, double()> variable; // NOTE: also made it a lexeme (no skipper)
    

    该规则仍然只公开值 作为副作用,我们将让它将引用收集到一组变量名称中:

            variable %=  
                   &qi::as_string[qi::raw[symbolTable]] 
                         [ px::insert(px::ref(collect_references), qi::_1) ] 
                >> symbolTable
                ;
    

    如您所见,这是一种利用许多 Spirit 技巧(operator%= 自动规则分配、qi::rawqi::as_string 指令、phoenix::insert 以及第二个使用积极的前瞻断言 (operator&amp;)。

    现在,我们只需要在语法中传入一个collect_references容器,解析成功后就可以打印引用了:

        std::set<std::string> collected_references;
        calculator calc(collected_references); // Our grammar
    
        if (r && iter == end)
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing succeeded\n";
            std::cout << "References: ";
            std::copy(collected_references.begin(), collected_references.end(),
                    std::ostream_iterator<std::string>(std::cout, " "));
    
            std::cout << "\nResult: " << eval(program) << std::endl;
            std::cout << "-------------------------\n";
        }
    

    打印出来:

    Type an expression...or [q or Q] to quit
    
    var* 2 - 3 +x*var2 - 2
    -------------------------
    Parsing succeeded
    References: var var2 x 
    Result: 80
    -------------------------
    Bye... :-) 
    

    演示代码

    Live On Coliru

    #include <boost/config/warning_disable.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/variant/recursive_variant.hpp>
    #include <boost/variant/apply_visitor.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/foreach.hpp>
    
    #include <iostream>
    #include <string>
    #include <set>
    
    namespace client {
        namespace ast
        {
            struct nil {};
            struct signed_;
            struct program;
    
            typedef boost::variant<
                nil
                , double
                , boost::recursive_wrapper<signed_>
                , boost::recursive_wrapper<program>
            >
            operand;
    
            struct signed_
            {
                char sign;
                operand operand_;
            };
    
            struct operation
            {
                char operator_;
                operand operand_;
            };
    
            struct program
            {
                operand first;
                std::list<operation> rest;
            };
        }
    }
    
    BOOST_FUSION_ADAPT_STRUCT(
        client::ast::signed_,
        (char, sign)
        (client::ast::operand, operand_)
        )
    
        BOOST_FUSION_ADAPT_STRUCT(
        client::ast::operation,
        (char, operator_)
        (client::ast::operand, operand_)
        )
    
        BOOST_FUSION_ADAPT_STRUCT(
        client::ast::program,
        (client::ast::operand, first)
        (std::list<client::ast::operation>, rest)
        )
    
    namespace client {
        namespace ast
        {
            struct eval
            {
                typedef double result_type;
    
                double operator()(nil) const { BOOST_ASSERT(0); return 0; }
                double operator()(double n) const { return n; }
    
                double operator()(operation const& x, double lhs) const
                {
                    double rhs = boost::apply_visitor(*this, x.operand_);
                    switch (x.operator_)
                    {
                    case '+': return lhs + rhs;
                    case '-': return lhs - rhs;
                    case '*': return lhs * rhs;
                    case '/': return lhs / rhs;
                    }
                    BOOST_ASSERT(0);
                    return 0;
                }
    
                double operator()(signed_ const& x) const
                {
                    double rhs = boost::apply_visitor(*this, x.operand_);
                    switch (x.sign)
                    {
                    case '-': return -rhs;
                    case '+': return +rhs;
                    }
                    BOOST_ASSERT(0);
                    return 0;
                }
    
                double operator()(program const& x) const
                {
                    double state = boost::apply_visitor(*this, x.first);
                    BOOST_FOREACH(operation const& oper, x.rest)
                    {
                        state = (*this)(oper, state);
                    }
                    return state;
                }
            };
        }
    }
    
    namespace client
    {
        namespace qi = boost::spirit::qi;
        namespace ascii = boost::spirit::ascii;
    
        template <typename Iterator>
        struct calculator : qi::grammar<Iterator, ast::program(), ascii::space_type>
        {
            calculator(std::set<std::string>& collect_references) : calculator::base_type(expression)
            {
                qi::char_type char_;
                qi::double_type doubleParser_;
    
                symbolTable.add("var1", 2);
                symbolTable.add("var2", 15);
                symbolTable.add("var4", 5);
                symbolTable.add("var",  5);
                symbolTable.add("x",    5);
    
                namespace px = boost::phoenix;
    
                expression =
                    term
                    >> *((char_('+') > term)
                     |  (char_('-') > term)
                    )
                    ;
    
                term =
                    factor
                    >> *((char_('*') > factor)
                    | (char_('/') > factor)
                    )
                    ;
    
                variable %=  
                       &qi::as_string[qi::raw[symbolTable]] 
                             [ px::insert(px::ref(collect_references), qi::_1) ] 
                    >> symbolTable
                    ;
    
                factor =
                    doubleParser_
                    | variable
                    | ('(' > expression > ')')
                    | (char_('-') > factor)
                    | (char_('+') > factor)
                    ;
            }
          private:
            qi::symbols<char, double> symbolTable;
            qi::rule<Iterator, double()> variable; // NOTE: also made it a lexeme (no skipper)
            qi::rule<Iterator, ast::program(), ascii::space_type> expression;
            qi::rule<Iterator, ast::program(), ascii::space_type> term;
            qi::rule<Iterator, ast::operand(), ascii::space_type> factor;
        };
    }
    
    /////////////////////////////////////////////////////////////////////////////
    //  Main program
    /////////////////////////////////////////////////////////////////////////////
    int
    main()
    {
        std::cout << "/////////////////////////////////////////////////////////\n\n";
        std::cout << "Expression parser...\n\n";
        std::cout << "/////////////////////////////////////////////////////////\n\n";
        std::cout << "Type an expression...or [q or Q] to quit\n\n";
    
        typedef std::string::const_iterator iterator_type;
        typedef client::calculator<iterator_type> calculator;
        typedef client::ast::program ast_program;
        typedef client::ast::eval ast_eval;
    
        std::string str;
        while (std::getline(std::cin, str))
        {
            if (str.empty() || str[0] == 'q' || str[0] == 'Q')
                break;
    
            std::set<std::string> collected_references;
            calculator calc(collected_references); // Our grammar
            ast_program program;                   // Our program (AST)
            ast_eval eval;                         // Evaluates the program
    
            std::string::const_iterator iter = str.begin();
            std::string::const_iterator end = str.end();
    
            boost::spirit::ascii::space_type space;
            bool r = phrase_parse(iter, end, calc, space, program);
    
            if (r && iter == end)
            {
                std::cout << "-------------------------\n";
                std::cout << "Parsing succeeded\n";
                std::cout << "References: ";
                std::copy(collected_references.begin(), collected_references.end(),
                        std::ostream_iterator<std::string>(std::cout, " "));
    
                std::cout << "\nResult: " << eval(program) << std::endl;
                std::cout << "-------------------------\n";
            }
            else
            {
                std::string rest(iter, end);
                std::cout << "-------------------------\n";
                std::cout << "Parsing failed\n";
                std::cout << "-------------------------\n";
            }
        }
    
        std::cout << "Bye... :-) \n\n";
        return 0;
    }
    

    【讨论】:

    【解决方案2】:

    第一个,涉及更多approach described here 修改 AST 以表示变量引用。

    这有很多优点:

    • 更丰富的信息允许您执行例如在解析之后的变量引用集合
    • 变量的延迟评估允许使用不同的变量值集重用相同的 AST!这更像是实际口译员的工作方式。

    让我们开始吧:

    1. 修改 AST:

      我们希望能够在现有的表达式类型之外有一个变量名:

      typedef boost::variant<
          nil
          , std::string // THIS LINE ADDED
          , double
          , boost::recursive_wrapper<signed_>
          , boost::recursive_wrapper<program>
      >
      operand;
      
    2. 修改规则:

      我们希望保留变量的名称,而不是立即将其替换为固定值:

          factor =
                qi::double_
              | qi::as_string[qi::raw[symbolTable]] // THIS LINE CHANGED
              | '(' > expression > ')'
              | (char_('-') > factor)
              | (char_('+') > factor)
              ;
      
    3. 修改评估访问者

      现在我们需要在评估期间用它们的值“替换”变量。

      让我们为访问者添加一个简单的重载:

      double operator()(std::string const& var) const { return symbols.at(var);    } 
      

      我们已为访问者提供了地图参考symbols 以供查找:

      std::map<std::string, double>& symbols;
      eval(std::map<std::string, double>& symbols) : symbols(symbols) {}
      
    4. main调用它:

      所以我们需要一个方便的变量映射:

      std::map<std::string, double> symbols { {"var1", 2}, {"var2", 15}, {"var4", 5}, {"var", 5}, {"x", 5} };
      

      并将引用传递给访问者:

      ast_eval eval(symbols) ; // Evaluates the program
      

    此时,程序与原始程序完全一样运行,但具有丰富的 AST:

    Live On Coliru

    打印

    -------------------------
    Parsing succeeded
    
    Result: 80
    -------------------------
    

    枚举引用的变量!

    故事的重点现在变得简单了:我们只需定义另一个访问者,例如 eval 来提取引用:

    namespace client { namespace ast {
        struct extract_refs : boost::static_visitor<void>
        {
            std::set<std::string>& _references;
    
            extract_refs(std::set<std::string>& refs) : _references(refs) {}
    
            void operator()(std::string const& var) const { _references.insert(var);                 } 
            void operator()(operation const& x) const     { boost::apply_visitor(*this, x.operand_); } 
            void operator()(signed_ const& x) const       { boost::apply_visitor(*this, x.operand_); } 
            void operator()(program const& x) const       {
                boost::apply_visitor(*this, x.first);
                BOOST_FOREACH(operation const& oper, x.rest) (*this)(oper);
            }
    
            // ignore anything else
            template <typename Other> void operator()(Other const&) const {}
        };
    } }
    

    这可以简单地应用到 AST:

    std::set<std::string> references;
    client::ast::extract_refs extract(references);
    
    extract(program);
    
    std::cout << "References: ";
    std::copy(references.begin(), references.end(), std::ostream_iterator<std::string>(std::cout, " "));
    

    输出又变成了

    -------------------------
    Parsing succeeded
    References: var var2 x 
    Result: 80
    -------------------------
    

    Quod Erat Demonstrandum。

    Live On Coliru

    【讨论】:

    • 非常感谢,真的很棒。但是,只是一个简单的说明,您忘记在提到的代码中包含 map,因此当您尝试在 VS2013 中编译代码时,您会收到编译器错误。再次感谢您,这对您很有帮助。
    猜你喜欢
    • 1970-01-01
    • 2012-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多