【问题标题】:Unhelpful compiler errors in x3 grammarx3 语法中无用的编译器错误
【发布时间】:2018-03-04 20:13:19
【问题描述】:

以下用于简单机器人命令语言的 Spirit x3 语法会在 Windows Visual Studio 17 中生成编译器错误。对于此项目,我需要在警告级别为 4 (/W4) 的情况下进行编译,并将警告视为错误 (/WX )。

警告 C4127 条件表达式为 常量 SpiritTest e:\data\boost\boost_1_65_1\boost\spirit\home\x3\char\detail\cast_char.hpp 29
错误 C2039“插入”:不是 'boost::spirit::x3::unused_type' SpiritTest e:\data\boost\boost_1_65_1\boost\spirit\home\x3\core\detail\parse_into_container.hpp 259 错误 C2039 'end': is not a member of 'boost::spirit::x3::unused_type' SpiritTest e:\data\boost\boost_1_65_1\boost\spirit\home\x3\core\detail\parse_into_container.hpp 259 错误 C2039 'empty': is not a member of 'boost::spirit::x3::unused_type' SpiritTest e:\data\boost\boost_1_65_1\boost\spirit\home\x3\core\detail\parse_into_container.hpp 254 错误 C2039 'begin': is not a member of 'boost::spirit::x3::unused_type' SpiritTest e:\data\boost\boost_1_65_1\boost\spirit\home\x3\core\detail\parse_into_container.hpp 259

显然,我的语法有问题,但错误消息完全没有帮助。我发现如果我删除语法最后一行中的 Kleene 星(* 参数到只是参数),错误就会消失,但是我会收到很多这样的警告:

“数字”的警告 C4459 声明隐藏全局 声明 SpiritTest e:\data\boost\boost_1_65_1\boost\spirit\home\x3\support\numeric_utils\detail\extract_int.hpp 174 警告 C4127 条件表达式为常量 SpiritTest e:\data\boost\boost_1_65_1\boost\spirit\home\x3\char\detail\cast_char.hpp 29

#include <string>
#include <iostream>

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;

//
// Grammar for simple command language
//

namespace scl
    {
    using boost::spirit::x3::char_;
    using boost::spirit::x3::double_;
    using boost::spirit::x3::int_;
    using boost::spirit::x3::lexeme;
    using boost::spirit::x3::lit;
    using boost::spirit::x3::no_case;

    auto    valid_identifier_chars = char_ ("a-zA-Z_");
    auto    quoted_string = '"' >> *(lexeme [~char_ ('"')]) >> '"';
    auto    keyword_value_chars = char_ ("a-zA-Z0-9$_.");

    auto    qual = lexeme [!(no_case [lit ("no")]) >> +valid_identifier_chars] >> -('=' >> (quoted_string | int_ | double_ | +keyword_value_chars));
    auto    neg_qual = lexeme [no_case [lit ("no")] >> +valid_identifier_chars];
    auto    qualifier = lexeme ['/' >> (qual | neg_qual)];

    auto    verb = +valid_identifier_chars >> *qualifier;
    auto    parameter = +keyword_value_chars >> *qualifier;
    auto    command = verb >> *parameter;
    };  // End namespace scl

using namespace std;                                    // Must be after Boost stuff!

int
main ()

{
vector <string> input = 
    {
    "show/out=\"somefile.txt\" motors/all cameras/full",
    "start/speed=5 motors arm1 arm2/speed=2.5/track arm3",
    "rotate camera1/notrack/axis=y/angle=45"
    };

    //
    // Parse each of the strings in the input vector
    //

    for (string str : input)
        {
        auto    b = str.begin ();
        auto    e = str.end ();


        cout << "Parsing: " << str << endl;
        x3::phrase_parse (b, e, scl::command, x3::space);

        if (b != e)
            {
            cout << "Error, only parsed to position: " << b - str.begin () << endl;
            }

        }   // End for

    return 0;
}                           // End main

【问题讨论】:

  • 如果骗子不是骗子,请告诉我。很可能是这样。您可以通过提供兼容属性而不是课程来“修复”它
  • @sehe,我看不出它是如何递归的。一个动词可以有零个或多个限定词,动词可以有零个或多个形参,每个形参有零个或多个限定词
  • 这个问题与递归规则无关 - 它只是在标题中出现,因为它是触发库错误的原因。
  • 今晚晚些时候我会解决的。中途在这里排练
  • 那里。正如我提到的,这完全是重复的,但我充实了一个解释。我通过复习语法并通过演示 Ast 完成它,使解释更有价值。

标签: c++ boost boost-spirit boost-spirit-x3


【解决方案1】:

自 Boost 1.65 以来有一个回归,这会导致一些可能传播到容器类型属性中的规则出现问题。

在没有实际绑定属性的情况下实例化时,它们会分派到错误的重载。当这种情况发生时,会有一个名为unused_type 的“模拟”属性类型。您看到的错误表明 unused_type 被视为一种具体的属性类型,显然这不会发生。

回归已修复在https://github.com/boostorg/spirit/commit/ee4943d5891bdae0706fb616b908e3bf528e0dfa

用 Boost 1.64 编译可以看出这是一个回归:

现在,最新的开发应该可以修复它,但您可以简单地复制修补文件,even just the 7-line patch

当我链接重复的问题How to make a recursive rule in boost spirit x3 in VS2017 时,以上所有内容都已经可用,这突出了相同的回归

评论

  • using namespace std; // Must be after Boost stuff!

    实际上,它可能不需要出现在任何地方,除非非常本地化,在那里您可以看到任何潜在名称合并的影响。

  • 考虑封装船长,因为从逻辑上讲,它很可能是语法规范的一部分,而不是被调用者覆盖的东西。

  • 这是一个错误:

    auto quoted_string = '"' >> *(lexeme[~char_('"')]) >> '"';
    

    您可能打算断言整个文字是词位,而不是单个字符(那是……没有实际意义,因为空格永远不会命中解析器,因为船长)。

    auto quoted_string = lexeme['"' >> *~char_('"') >> '"'];
    
  • 1234563
  • x3::space 跳过嵌入的换行符,如果这不是本意,请使用 x3::blank

  • 由于 PEG 语法是从左到右解析的贪心,因此您可以订购 qualifier 产生式,而无需使用 !(no_case["no"]) 前瞻断言。这不仅消除了重复,而且使语法更简单、更高效:

    auto qual      = lexeme[+valid_identifier_chars] >>
        -('=' >> (quoted_string | int_ | double_ | +keyword_value_chars)); // TODO lexeme
    auto neg_qual  = lexeme[no_case["no"] >> +valid_identifier_chars];
    auto qualifier = lexeme['/' >> (neg_qual | qual)];
    

    ¹ 注意(Post-Scriptum)现在我们注意到qualifier 本身已经是一个词位,没有必要在里面添加lexeme[] 东西(当然,除非它们被重复使用在有船长的情况下)。

    然而,这也引发了一个问题,是否应该接受 = 运算符周围的空格(目前不接受),或者是否可以用空格分隔限定符(如 id /a /b;目前可以)。

  • 也许verb 也需要一些lexemes[](除非您真的确实想将"one two three" 解析为动词)

  • 如果 no 前缀为否定限定符,那么 也许标识符本身也是?这样可以简化语法

  • int_double_ 的排序使得大多数双精度词在被识别之前被错误解析为 int。考虑更明确的内容,例如 x3::strict_real_policies&lt;double&gt;&gt;{} | int_

  • 如果您正在解析带引号的构造,也许您也想识别转义(例如'\"''\\'):

    auto quoted_string = lexeme['"' >> *('\\' >> char_ | ~char_('"')) >> '"'];
    
  • 如果您需要“关键字值”,请考虑在 x3::symbols&lt;&gt; 中列出已知值。这也可以用来直接解析成枚举类型。

这是一个解析为 AST 类型并将其打印回来用于演示目的的版本:

Live On Coliru

#include <boost/config/warning_disable.hpp>

#include <string>
#include <vector>
#include <boost/variant.hpp>

namespace Ast {
    struct Keyword : std::string { // needs to be strong-typed to distinguish from quoted values
        using std::string::string;
        using std::string::operator=;
    };

    struct Nil {};
    using Value = boost::variant<Nil, std::string, int, double, Keyword>;

    struct Qualifier {
        enum Kind { positive, negative } kind;
        std::string identifier;
        Value       value;
    };

    struct Param {
        Keyword                keyword;
        std::vector<Qualifier> qualifiers;
    };

    struct Command {
        std::string            verb;
        std::vector<Qualifier> qualifiers;
        std::vector<Param>     params;
    };
}

#include <boost/fusion/adapted/struct.hpp>
BOOST_FUSION_ADAPT_STRUCT(Ast::Qualifier, kind, identifier, value)
BOOST_FUSION_ADAPT_STRUCT(Ast::Param, keyword, qualifiers)
BOOST_FUSION_ADAPT_STRUCT(Ast::Command, verb, qualifiers, params)

#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;
namespace scl {
    //
    // Grammar for simple command language
    //
    using x3::char_;
    using x3::int_;
    using x3::lexeme;
    using x3::no_case;

    // lexeme tokens
    auto keyword       = x3::rule<struct _keyword, Ast::Keyword> { "keyword" }
                       = lexeme [ +char_("a-zA-Z0-9$_.") ];
    auto identifier    = lexeme [ +char_("a-zA-Z_") ];
    auto quoted_string = lexeme['"' >> *('\\' >> x3::char_ | ~x3::char_('"')) >> '"'];

    auto value
        = quoted_string
        | x3::real_parser<double, x3::strict_real_policies<double>>{}
        | x3::int_
        | keyword;

    auto qual
        = x3::attr(Ast::Qualifier::positive) >> identifier >> -('=' >> value);
    auto neg_qual
        = x3::attr(Ast::Qualifier::negative) >> lexeme[no_case["no"] >> identifier] >> x3::attr(Ast::Nil{}); // never a value
    auto qualifier 
        = lexeme['/' >> (neg_qual | qual)];

    auto verb      
        = identifier;

    auto parameter = x3::rule<struct _parameter, Ast::Param> {"parameter"}
        = keyword >> *qualifier;

    auto command = x3::rule<struct _command, Ast::Command> {"command"} 
        = x3::skip(x3::space) [ verb >> *qualifier >> *parameter ];

} // End namespace scl

// For Demo, Debug: printing the Ast types back
#include <iostream>
#include <iomanip>

namespace Ast {
    static inline std::ostream& operator<<(std::ostream& os, Value const& v) {
        struct {
            std::ostream& _os;
            void operator()(std::string const& s) const { _os << std::quoted(s); }
            void operator()(int i)                const { _os << i; } 
            void operator()(double d)             const { _os << d; } 
            void operator()(Keyword const& kwv)   const { _os << kwv; } 
            void operator()(Nil)                  const { }
        } vis{os};

        boost::apply_visitor(vis, v);
        return os;
    }

    static inline std::ostream& operator<<(std::ostream& os, Qualifier const& q) {
        os << "/" << (q.kind==Qualifier::negative?"no":"") << q.identifier;
        if (q.value.which())
           os << "=" << q.value;
        return os;
    }

    static inline std::ostream& operator<<(std::ostream& os, std::vector<Qualifier> const& qualifiers) {
        for (auto& qualifier : qualifiers)
            os << qualifier;
        return os;
    }

    static inline std::ostream& operator<<(std::ostream& os, Param const& p) {
        return os << p.keyword << p.qualifiers;
    }

    static inline std::ostream& operator<<(std::ostream& os, Command const& cmd) {
        os << cmd.verb << cmd.qualifiers;
        for (auto& param : cmd.params)         os << " " << param;
        return os;
    }
}

int main() {
    for (std::string const str : {
            "show/out=\"somefile.txt\" motors/all cameras/full",
            "start/speed=5 motors arm1 arm2/speed=2.5/track arm3",
            "rotate camera1/notrack/axis=y/angle=45",
        })
    {
        auto b = str.begin(), e = str.end();

        Ast::Command cmd;
        bool ok = parse(b, e, scl::command, cmd);
        std::cout << (ok?"OK":"FAIL") << '\t' << std::quoted(str) << '\n';

        if (ok) {
            std::cout << " -- Full AST: " << cmd << "\n";
            std::cout << " -- Verb+Qualifiers: " << cmd.verb << cmd.qualifiers << "\n";
            for (auto& param : cmd.params)
                std::cout << "     -- Param+Qualifiers: " << param << "\n";
        }

        if (b != e) {
            std::cout << " -- Remaining unparsed: " << std::quoted(std::string(b,e)) << "\n";
        }
    }
}

打印

OK  "show/out=\"somefile.txt\" motors/all cameras/full"
 -- Full AST: show/out="somefile.txt" motors/all cameras/full
 -- Verb+Qualifiers: show/out="somefile.txt"
     -- Param+Qualifiers: motors/all
     -- Param+Qualifiers: cameras/full
OK  "start/speed=5 motors arm1 arm2/speed=2.5/track arm3"
 -- Full AST: start/speed=5 motors arm1 arm2/speed=2.5/track arm3
 -- Verb+Qualifiers: start/speed=5
     -- Param+Qualifiers: motors
     -- Param+Qualifiers: arm1
     -- Param+Qualifiers: arm2/speed=2.5/track
     -- Param+Qualifiers: arm3
OK  "rotate camera1/notrack/axis=y/angle=45"
 -- Full AST: rotate camera1/notrack/axis=y/angle=45
 -- Verb+Qualifiers: rotate
     -- Param+Qualifiers: camera1/notrack/axis=y/angle=45

为了完整性

  • 演示也Live On MSVC (Rextester) - 请注意 RexTester 使用 Boost 1.60
  • Coliru 使用 Boost 1.66 问题并没有表现出来,因为现在,解析器绑定了具体的属性值

【讨论】:

  • 哇,这是一个了不起的回应。非常感谢您花时间教我如何做到这一点!显然,我对 x3 的理解是初步的,但很高兴知道这不全是我的错。我计划对动词、限定词和关键字使用 x3::symbol,但我只是想让一个基本的解析器工作。我还想实现缩写(V、Q、K)。使用 x3::symbol 是最好的方法吗?为什么“run /q1 /q2 p1 /q3 = abc p2”的输入无法正确解析?标识符规则不包含'/',为什么动词不会在'/'处停止匹配?
  • "/q3 = abc" 不应解析,因为 q3 不是有效标识符。此外,就像我在上面的注意中提到的那样:= 周围的空格目前不被接受。
  • Re.: “标识符规则不包含'/',那么为什么动词不会在'/'处停止匹配?” - 呵呵,在哪里你看到那个问题了吗?仔细看实际输出:I don't think it says anything like you expect it to say
  • 与您交谈就像与禅师交谈。你所说的一切都是绝对正确的,但是我自己的先入之见和对该主题的有限知识使我无法立即理解;-) 那么,实现动词、限定词和关键字的缩写的最佳方法是什么?我的想法是提供一个有效字符串列表,然后让一个例程生成缩写并将它们放入 x3::symbol
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-01
  • 2011-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多