[英]How to get the substring matched by a boost.spirit numeric parser?
[英]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僅包含值節點而不是原始引用)。
有兩種解決方法:
后者所需的精力要小得多(如果您知道Spirit + Phoenix的技巧)。 因此,讓我們展示一下:
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::raw
和qi::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... :-)
#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:
我們希望除了現有的表達式類型之外,還能夠擁有一個變量名:
typedef boost::variant< nil , std::string // THIS LINE ADDED , double , boost::recursive_wrapper<signed_> , boost::recursive_wrapper<program> > operand;
修改規則:
我們想保留變量名 ,而不是立即用固定值替換它:
factor = qi::double_ | qi::as_string[qi::raw[symbolTable]] // THIS LINE CHANGED | '(' > expression > ')' | (char_('-') > factor) | (char_('+') > factor) ;
修改評估訪問者
現在我們需要在評估期間通過變量的值“替換”變量。
讓我們為訪問者添加一個簡單的重載:
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) {}
從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:
列印
-------------------------
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演示。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.