Boost C++ 库

...世界上评价最高、设计最精良的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

文法 - Boost C++ 函数库
文法

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

我们上面的文法可以被实例化并投入使用。

    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 发行版的一部分。

self

您可能会注意到,文法的定义有一个接受外部文法常量引用的构造函数。在上面的示例中,请注意calculator::definition接收了一个calculator const& self。虽然在上面的示例中它没有被使用,但在许多情况下,这非常有用。self 参数是定义对外部世界的窗口。例如,计算器类可能有一个指向某些状态信息的引用,定义可以在通过 语义动作 进行解析时更新该状态信息。

文法胶囊

随着文法变得越来越复杂,将部分内容分组到逻辑模块中是一个好主意。例如,在编写一种语言时,将表达式和语句放入单独的文法胶囊中可能是明智的。文法利用了 C++ 类的封装特性。类的声明式特性使其非常适合文法的定义。由于文法不过是一个类声明,我们可以方便地将其发布在头文件中。其理念是,一旦编写并经过全面测试,文法就可以在许多上下文中重用。我们现在有了文法库的概念。

重入性和多线程

一个文法实例可以在多个地方被多次使用而不会出现任何问题。该实现经过优化,允许这样做,但会付出一些额外的开销。但是,如果我们确定一个文法只有一个实例,那么我们可以节省大量的周期和字节。如果需要,只需定义BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE,然后在包含任何 spirit 头文件之前。

    #define BOOST_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不能用作第一个模板参数。

grammar_def 起始类型

可能并不明显,但有趣的是,除了 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()函数调用中的规则列表的零基索引。

use_parser<0>

请注意,使用 0(零)作为模板参数到use_parser等同于使用起始规则,该规则通过常规方式通过start()函数导出,如第一个calculator示例所示。因此,即使对于仅通过其start()函数导出一个规则的文法,也可以使用此表示法。另一方面,调用语法而不使用use_parser表示法将执行作为start_parsers()函数实现的。

可用的起始规则的最大数量受预处理器常量

    BOOST_SPIRIT_GRAMMAR_STARTRULE_TYPE_LIMIT // defaults to 3