如何在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 << "/////////////////////////////////////////////////////////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::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 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使用不同的变量值集进行重用!这更像是真正的解释器的工作方式。
让我们开始:
-
修改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:
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
- 如何打印boost多精度128位无符号整数
- C ++如何使用UTF8十六进制代码打印UTF8符号?
- 首先按给定顺序打印所有数字,然后使用 Array 打印所有字符和其他符号
- 将浮点数转换为无符号字符数组并打印出来
- 如何打印大于"无符号长长"的"std::bitset"的十进制值?
- 无符号字符打印其 ASCII 值
- 在 c++ 中以十六进制格式打印无符号字符(BYTE).使用 std::cout
- 在我的变量打印C++后无法获得 $ 打印符号
- 如何通过输入十进制数打印UTF-8符号?
- 编写一个程序,输入整数 n 并打印该数字的字符数(符号除外)
- uint8_t在转换为无符号 int 时未打印正确的值
- 从C++文件中读取和打印 UTF-8 符号
- 如何打印 ;符号使用 CMake 命令
- 打印链表返回随机符号
- 为什么C++将无符号字符值打印为负数?
- 使用具有 UTF8 编码的源文件将 UTF8 符号打印到 Windows 控制台
- 将怪异的符号打印到输出文件C
- 程序将怪异的符号打印到输出文件C
- 奇怪的符号打印出来而不是字符串
- 用箭头符号打印圆圈