簡體   English   中英

如何在Boost精神分析器中打印與符號表匹配的變量?

[英]How to print the variables matched by the symbol table in Boost spirit parser?

我是使用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

有什么幫助嗎?

使用的AST不存儲引用的原始變量。

因此, 解析之后,該信息不再可用(AST僅包含值節點而不是原始引用)。

有兩種解決方法:

  • 豐富AST,以便您僅在評估時解析變量(保留變量引用名稱)

    更新我添加了另一個答案 ,該答案實際上實現了這種更精細的方法

  • 讓解析器在解析過程中“帶外”收集變量引用。

后者所需的精力要小得多(如果您知道Spirit + Phoenix的技巧)。 因此,讓我們展示一下:

        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& )。

現在,我們只需要將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... :-) 

演示代碼

生活在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;
}

這里描述的第一個更復雜的方法是修改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:

生活在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演示。

生活在Coliru

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM