如何在Boost精灵解析器中打印符号表匹配的变量

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

本文关键字:符号 打印 变量 Boost 精灵      更新时间:2023-10-16

我是使用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 << "/////////////////////////////////////////////////////////nn";
    std::cout << "Expression parser...nn";
    std::cout << "/////////////////////////////////////////////////////////nn";
    std::cout << "Type an expression...or [q or Q] to quitnn";
    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 succeededn";
            std::cout << "nResult: " << eval(program) << std::endl;
            std::cout << "-------------------------n";
        }
        else
        {
            std::string rest(iter, end);
            std::cout << "-------------------------n";
            std::cout << "Parsing failedn";
            std::cout << "-------------------------n";
        }
    }
    std::cout << "Bye... :-) nn";
    return 0;
}

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

例如当输入是

var* 2 - 3 +x*var2 - 2

输出应该是:

var
x
var2

帮忙吗?

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

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

有两种方法:

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

    UPDATE我添加了另一个答案,它实际上实现了这个更详细的方法

  • 让解析器在解析时收集"带外"变量引用。

后者需要更小的努力(如果你知道精神+凤凰的技巧)。我们来看一下:

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

这里我用一个新规则替换了symbolTable: variable:

    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 succeededn";
        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 << "/////////////////////////////////////////////////////////nn";
    std::cout << "Expression parser...nn";
    std::cout << "/////////////////////////////////////////////////////////nn";
    std::cout << "Type an expression...or [q or Q] to quitnn";
    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 succeededn";
            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 failedn";
            std::cout << "-------------------------n";
        }
    }
    std::cout << "Bye... :-) nn";
    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:

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
-------------------------

《上帝的示范》

生活在Coliru