解析除关键字以外的标识符

parsing identifiers except keywords

本文关键字:标识符 关键字      更新时间:2023-10-16

我正在努力编写一个标识符解析器,它解析一个不是关键字的字母字符串。关键字都在一个表中:

struct keywords_t : x3::symbols<x3::unused_type> {
    keywords_t() {
        add("for", x3::unused)
                ("in", x3::unused)
                ("while", x3::unused);
    }
} const keywords;

和标识符的解析器应该是这样的:

auto const identifier_def =       
            x3::lexeme[
                (x3::alpha | '_') >> *(x3::alnum | '_')
            ];

现在我尝试将这些组合起来,以便标识符解析器在解析关键字时失败。我试着这样做:

auto const identifier_def =       
                x3::lexeme[
                    (x3::alpha | '_') >> *(x3::alnum | '_')
                ]-keywords;

:

auto const identifier_def =       
                x3::lexeme[
                    (x3::alpha | '_') >> *(x3::alnum | '_') - keywords
                ];

它适用于大多数输入,但如果字符串以关键字开头,如int, whilefoo, forbar,解析器无法解析该字符串。如何使解析器正确?

您的问题是由Spirit中差异操作符的语义引起的。当您有a - b时,Spirit执行以下操作:

  • 检查b是否匹配:
    • 如果是,a - b失败,没有任何解析。
    • 如果b失败,则检查a是否匹配:
      • 如果a失败,则a - b失败,并且不解析任何内容。
      • 如果a成功,则a - b成功,并解析a解析的内容。

在您的情况下(unchecked_identifier - keyword),只要标识符以关键字开头,keyword将匹配,解析器将失败。因此,您需要将keyword交换为在传递不同关键字时匹配的内容,但在关键字后跟其他内容时失败。not predicate (!)可以帮助解决这个问题。

auto const distinct_keyword = x3::lexeme[ keyword >> !(x3::alnum | '_') ];

完整样本(在Coliru上运行):

//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/spirit/home/x3.hpp>
namespace parser {
    namespace x3 = boost::spirit::x3;
    struct keywords_t : x3::symbols<x3::unused_type> {
        keywords_t() {
            add("for", x3::unused)
                    ("in", x3::unused)
                    ("while", x3::unused);
        }
    } const keywords;
    x3::rule<struct identifier_tag,std::string>  const identifier ("identifier");
    auto const distinct_keyword = x3::lexeme[ keywords >> !(x3::alnum | '_') ];
    auto const unchecked_identifier = x3::lexeme[(x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_'))];

    auto const identifier_def = unchecked_identifier - distinct_keyword;
    //This should also work:
    //auto const identifier_def = !distinct_keyword >> unchecked_identifier

    BOOST_SPIRIT_DEFINE(identifier);
    bool is_identifier(const std::string& input)
    {
        auto iter = std::begin(input), end= std::end(input);
        bool result = x3::phrase_parse(iter,end,identifier,x3::space);
        return result && iter==end;
    }
}

int main() {
    std::cout << parser::is_identifier("fortran") << std::endl;
    std::cout << parser::is_identifier("for") << std::endl;
    std::cout << parser::is_identifier("integer") << std::endl;
    std::cout << parser::is_identifier("in") << std::endl;
    std::cout << parser::is_identifier("whileechoyote") << std::endl;
    std::cout << parser::is_identifier("while") << std::endl;
}

问题是,它在没有词法分析器的情况下运行,也就是说,如果您写入

keyword >> *char_

输入whilefoo,它会将while解析为keyword,将foo解析为*char_

可以用两种方法来防止:要么要求在关键字后面有一个空格,即

auto keyword_rule = (keyword >> x3::space);
//or if you use phrase_parse
auto keyword_rule = x3::lexeme[keyword >> x3::space];

你描述的另一种方式也是可能的,即明确地从字符串中删除关键字(我会这样做):

auto string = x3::lexeme[!keyword >> (x3::alpha | '_') >> *(x3::alnum | '_')];

您的定义的问题在于,它将把第一组字符解释为关键字,从而选择根本不解析它。'x-y'操作符的意思是解析x,而不是y。但是如果你传递'whilefoo',它会将'while'解释为关键字,因此根本不解析。