| 文法 |
![]() |
![]() |
![]() |
grammar 封装了一组规则。该语法类是一个协议基类。它本质上是一个接口约定。该语法是一个模板类,它通过其派生类,DerivedT和它的上下文,ContextT来参数化。模板参数 ContextT 默认情况下是parser_context,一个预定义的上下文。
您无需过多关注 ContextT 模板参数,除非您希望调整文法的底层行为。有关 ContextT 模板参数的详细信息将在 其他地方 提供。该语法依赖于模板参数 DerivedT,即一个文法子类来定义实际的规则。
下面是公共 API。在ContextT之后可能还有更多的模板参数。在ContextT参数之后的内容不应由客户端关心,并且仅供内部使用。
template<
typename DerivedT,
typename ContextT = parser_context<> >
struct grammar;
一个继承自语法的具体子类应该有一个名为定义:
它是一个嵌套模板类,其中有一个类型名称ScannerT参数。
它的构造函数定义了文法规则。
它的构造函数接收一个指向实际文法的引用self.
它有一个名为开始的成员函数,该函数返回一个指向起始rule.
struct my_grammar : public grammar<my_grammar>
{
template <typename ScannerT>
struct definition
{
rule<ScannerT> r;
definition(my_grammar const& self) { r = /*..define here..*/; }
rule<ScannerT> const& start() const { return r; }
};
};
将扫描器类型与构成文法的规则解耦,允许文法在可能使用不同扫描器的不同上下文中重用。我们不关心我们正在处理哪种扫描器。用户定义的my_grammar可以与**任何**类型的扫描器一起使用。与规则不同,文法不绑定到特定的扫描器类型。有关为什么这很重要以及更深入地理解这种扫描器-规则耦合问题,请参阅 “扫描器业务”。
我们上面的文法可以被实例化并投入使用。
my_grammar g;
if (parse(first, last, g, space_p).full)
cout << "parsing succeeded\n";
else
cout << "parsing failed\n";
my_grammar 是一种解析器,并且可以在任何需要解析器的地方使用,甚至可以被另一个规则引用。
rule<> r = g >> str_p("cool huh?");
与规则一样,当文法放在 EBNF 表达式的右侧时,它也通过引用来保存。客户端有责任确保被引用的文法保持在作用域内,并且在被引用时不会被销毁。 |
回顾我们最初的计算器示例,现在用文法重写如下:
struct calculator : public grammar<calculator>
{
template <typename ScannerT>
struct definition
{
definition(calculator const& self)
{
group = '(' >> expression >> ')';
factor = integer | group;
term = factor >> *(('*' >> factor) | ('/' >> factor));
expression = term >> *(('+' >> term) | ('-' >> term));
}
rule<ScannerT> expression, term, factor, group;
rule<ScannerT> const&
start() const { return expression; }
};
};
包含 语义动作 的完整工作示例可以在 这里 查看。这是 Spirit 发行版的一部分。
您可能会注意到,文法的定义有一个接受外部文法常量引用的构造函数。在上面的示例中,请注意calculator::definition接收了一个calculator const& self。虽然在上面的示例中它没有被使用,但在许多情况下,这非常有用。self 参数是定义对外部世界的窗口。例如,计算器类可能有一个指向某些状态信息的引用,定义可以在通过 语义动作 进行解析时更新该状态信息。 |
随着文法变得越来越复杂,将部分内容分组到逻辑模块中是一个好主意。例如,在编写一种语言时,将表达式和语句放入单独的文法胶囊中可能是明智的。文法利用了 C++ 类的封装特性。类的声明式特性使其非常适合文法的定义。由于文法不过是一个类声明,我们可以方便地将其发布在头文件中。其理念是,一旦编写并经过全面测试,文法就可以在许多上下文中重用。我们现在有了文法库的概念。
一个文法实例可以在多个地方被多次使用而不会出现任何问题。该实现经过优化,允许这样做,但会付出一些额外的开销。但是,如果我们确定一个文法只有一个实例,那么我们可以节省大量的周期和字节。如果需要,只需定义BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE,然后在包含任何 spirit 头文件之前。
#defineBOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE
另一方面,如果一个文法打算在多线程代码中使用,那么我们应该定义BOOST_SPIRIT_THREADSAFE,然后在包含任何 spirit 头文件之前。在这种情况下,还需要链接 Boost.Threads。
#define BOOST_SPIRIT_THREADSAFE
有时,希望一个文法有多个可见的入口点(除了起始规则)。为了允许额外的起始点,Spirit 提供了一个辅助模板grammar_def,它可以作为您定义的子类的基类。语法下面是一个例子:
// this header has to be explicitly included
#include <boost/spirit/utility/grammar_def.hpp>
struct calculator2 : public grammar<calculator2>
{
enum
{
expression = 0,
term = 1,
factor = 2,
};
template <typename ScannerT>
struct definition
: public grammar_def<rule<ScannerT>, same, same>
{
definition(calculator2 const& self)
{
group = '(' >> expression >> ')';
factor = integer | group;
term = factor >> *(('*' >> factor) | ('/' >> factor));
expression = term >> *(('+' >> term) | ('-' >> term));
this->start_parsers(expression, term, factor);
}
rule<ScannerT> expression, term, factor, group;
};
};
该grammar_def模板必须使用您希望从语法:
grammar_def<rule<ScannerT>, same, same>
简写符号same来指示使用与前一个模板参数指定的相同类型(例如,rule<ScannerT>)。显然,same不能用作第一个模板参数。
| 可能并不明显,但有趣的是,除了 rule<> 之外,任何解析器类型都可以指定(例如,chlit<>、strlit<>、int_parser<> 等)。 |
使用 grammar_def 类,不再需要提供start()成员函数。取而代之的是,您需要调用this->start_parsers()(它是grammar_def模板的成员函数),以定义您的语法的起始符号。
注意,传递给start_parsers()函数的规则的数量和顺序应与grammar_deftemplate
this->start_parsers(expression, term, factor);
可以使用以下语法指定文法入口点:
g.use_parser<N>() // Where g is your grammar and N is the Nth entry.
此示例显示了如何使用项规则来自上面的calculator2文法。
calculator2 g;
if (parse(
first, last,
g.use_parser<calculator2::term>(),
space_p
).full)
{
cout << "parsing succeeded\n";
}
else {
cout << "parsing failed\n";
}
模板参数use_parser<>模板类型应该是指定在start_parsers()函数调用中的规则列表的零基索引。
请注意,使用 0(零)作为模板参数到use_parser等同于使用起始规则,该规则通过常规方式通过start()函数导出,如第一个calculator示例所示。因此,即使对于仅通过其start()函数导出一个规则的文法,也可以使用此表示法。另一方面,调用语法而不使用use_parser表示法将执行作为start_parsers()函数实现的。 |
可用的起始规则的最大数量受预处理器常量
BOOST_SPIRIT_GRAMMAR_STARTRULE_TYPE_LIMIT // defaults to 3
![]() |
![]() |
![]() |
版权所有 © 1998-2003 Joel de Guzman
Copyright © 2003-2004 Hartmut Kaiser
使用、修改和分发受 Boost Software License Version 1.0 的约束。(请参阅随附文件 LICENSE_1_0.txt 或访问 https://boost.ac.cn/LICENSE_1_0.txt 复制)