在boost-spirit中是否有通过函数调用来创建语法以减少代码的方法?

huangapple go评论71阅读模式
英文:

In boost-spirit is there any way to create grammars by function call in order to reduce code?

问题

以下是已经翻译好的代码部分:

#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/qi_no_case.hpp>
#include <boost/phoenix/bind/bind_function.hpp>
#include <boost/phoenix/bind/bind_function_object.hpp>
#include <boost/phoenix/operator.hpp>

namespace test
{
namespace qi = boost::spirit::qi;
template<typename IT>
auto make_keyword_parser(const char *const _p, const bool _b)
{ return (qi::eps(_b) >> qi::lit(_p)) | (qi::eps(!_b) >> qi::no_case[qi::lit(_p)]);
}
template<typename IT>
struct booleanParser:qi::grammar<IT, bool(), qi::space_type>
{ qi::rule<IT, bool(), qi::space_type> m_sStart;
  booleanParser(const bool _b)
    :booleanParser::base_type(m_sStart, "booleanParser")
  { m_sStart = make_keyword_parser<IT>("true", _b)[qi::_val = true]
      | make_keyword_parser<IT>("false", _b)[qi::_val = false];
  }
};
}
int main()
{
  namespace qi = boost::spirit::qi;
  test::booleanParser<const char*> sGrammar(false);
  bool b;
  static constexpr char ac[] = "True";
  auto p = ac;
  if (!boost::spirit::qi::phrase_parse(p, ac + sizeof ac - 1, sGrammar, qi::space_type(), b) || p != ac + sizeof ac - 1)
    std::cerr << "error" << std::endl;
  else
    std::cerr << "b=" << b << std::endl;
}

请注意,翻译后的代码仅包括代码部分,不包括问题或其他内容。

英文:

The following code crashes. I suspect that the grammar cannot be copied.

#include &lt;iostream&gt;
#include &lt;boost/spirit/include/qi.hpp&gt;
#include &lt;boost/spirit/include/qi_no_case.hpp&gt;
#include &lt;boost/phoenix/bind/bind_function.hpp&gt;
#include &lt;boost/phoenix/bind/bind_function_object.hpp&gt;
#include &lt;boost/phoenix/operator.hpp&gt;


namespace test
{
namespace qi = boost::spirit::qi;
template&lt;typename IT&gt;
auto make_keyword_parser(const char *const _p, const bool _b)
{	return (qi::eps(_b) &gt;&gt; qi::lit(_p)) | (qi::eps(!_b) &gt;&gt; qi::no_case[qi::lit(_p)]);
}
template&lt;typename IT&gt;
struct booleanParser:qi::grammar&lt;IT, bool(), qi::space_type&gt;
{	qi::rule&lt;IT, bool(), qi::space_type&gt; m_sStart;
	booleanParser(const bool _b)
		:booleanParser::base_type(m_sStart, &quot;booleanParser&quot;)
	{	m_sStart = make_keyword_parser&lt;IT&gt;(&quot;true&quot;, _b)[qi::_val = true]
			| make_keyword_parser&lt;IT&gt;(&quot;false&quot;, _b)[qi::_val = false];
	}
};
}
int main()
{
	namespace qi = boost::spirit::qi;
	test::booleanParser&lt;const char*&gt; sGrammar(false);
	bool b;
	static constexpr char ac[] = &quot;True&quot;;
	auto p = ac;
	if (!boost::spirit::qi::phrase_parse(p, ac + sizeof ac - 1, sGrammar, qi::space_type(), b) || p != ac + sizeof ac - 1)
		std::cerr &lt;&lt; &quot;error&quot; &lt;&lt; std::endl;
	else
		std::cerr &lt;&lt; &quot;b=&quot; &lt;&lt; b &lt;&lt; std::endl;
}

The code works fine, if the body of the make_keyword_parser() function consists only of return qi::lit(_p);.

It looks like your post is mostly code; please add some more details.
It looks like your post is mostly code; please add some more details.
It looks like your post is mostly code; please add some more details.

答案1

得分: 2

没有阅读您的代码,但我过去处理过多个非常相似的问题。

简而言之,很容易意外触发未定义行为,因为Phoenix表达式并不是为了命名而设计的,所以它们通常包含对临时变量的引用。

以下是一些相关的链接,可能会对您特别有兴趣:

此外,还有其他一些链接:

实际上,所有这些帖子都会花一些时间来避免未定义行为的陷阱,所以请谨慎行事。

现在让我阅读您的代码,看看是否可以为您添加一些特定的信息。

回顾

实际上,您的代码也遇到了未定义行为。您可以通过在返回时请求Phoenix表达式的深层副本来避免它:

return qi::copy((qi::eps(_b) >> _p) | (qi::eps(!_b) >> qi::no_case[_p]));

qi::copy 只是 boost::proto::deep_copy 的一个便捷方式。

还有一些建议:

  • 不要公开跳过器。调用者没有权利覆盖您的跳过器,它属于语法。
  • 不要在词元上使用跳过器(boolParser 是一个词元)。
  • 在解析器中指定 qi::eoi,而不要在事后检查迭代器。
  • 您的 make_keyword_parser 做了一些奇怪的事情,可以考虑返回一个静态规则,这样您就不需要反复支付运行时成本。

总之,希望这些信息对您有所帮助。

英文:

Without reading your code, I've treated multiple very similar questions in the past.

The TL;DR is "Here be dragons" as it is very easy to accidentally invoke Undefined Behaviour due the fact that Phoenix expressions are not meant to be named, so liberally contain references to temporaries.

Practically all these posts will spend some time avoiding the UB traps, so caveat emptor.

Now let me read the question code and see whether I can add something specific to that.

Review

Indeed your code also suffered the UB. You can avoid it by requesting a deep-copy of your phoenix-expression when you return it:

return qi::copy((qi::eps(_b) &gt;&gt; _p) | (qi::eps(!_b) &gt;&gt; qi::no_case[_p]));

qi::copy is just a convenience shorthand for boost::proto::deep_copy.

Live On Coliru

#include &lt;boost/phoenix.hpp&gt;
#include &lt;boost/spirit/include/qi.hpp&gt;
#include &lt;iostream&gt;
using namespace std::string_view_literals;
namespace qi = boost::spirit::qi;

namespace test {
    template &lt;typename IT&gt; auto make_keyword_parser(char const* const _p, bool const _b) {
        return qi::copy((qi::eps(_b) &gt;&gt; _p) | (qi::eps(!_b) &gt;&gt; qi::no_case[_p]));
    }

    template &lt;typename It&gt; struct booleanParser : qi::grammar&lt;It, bool()&gt; {
        booleanParser(bool const _b) : booleanParser::base_type(m_sStart, &quot;booleanParser&quot;) {
            m_sStart = make_keyword_parser&lt;It&gt;(&quot;true&quot;, _b)[qi::_val = true] |
                make_keyword_parser&lt;It&gt;(&quot;false&quot;, _b)[qi::_val = false];
        }

      private:
        qi::rule&lt;It, bool()&gt; m_sStart;
    };
} // namespace test

int main() {
    for (bool opt : {true, false}) {
        std::cout &lt;&lt; &quot; --- Opt: &quot; &lt;&lt; std::boolalpha &lt;&lt; opt &lt;&lt; std::endl;
        test::booleanParser&lt;char const*&gt; sGrammar(opt);

        static constexpr auto txt = &quot;True&quot;sv;

        if (bool b; parse(begin(txt), end(txt), sGrammar &gt;&gt; qi::eoi, b))
            std::cout &lt;&lt; &quot;b=&quot; &lt;&lt; b &lt;&lt; std::endl;
        else
            std::cout &lt;&lt; &quot;error&quot; &lt;&lt; std::endl;
    }
}

Prints

 --- Opt: true
error
 --- Opt: false
b=true

I'd be remiss if I didn't make a few remarks:

  • don't expose the skipper. The caller has no business overriding your skipper, it belongs to the grammar

  • don't use a skipper on a lexeme (boolParser is a lexeme)

  • don't check of iterators after the fact, specify qi::eoi in the parser

  • Your make_keyword_parser does a few strange things:

    • it doesn't create a keyword parser (a keyword parser would logically check that the end of the match is on a word/identifier boundary). I've shown you before how to do it (commenting that particular purpose in the code). Probably on more than one occasion (I forget) but certainly in my last answer to you 在boost-spirit中是否有通过函数调用来创建语法以减少代码的方法?

    • it creates a dynamica rule, where the _b argument isn't dynamic. Consider just returning a static rule, something like

      template &lt;typename Attr = qi::unused_type, typename It&gt;
      auto const&amp; make_keyword_parser(char const* const _p, bool const _b) {
          static const qi::rule&lt;It, Attr()&gt; //
              pcs = _p,                     //
              pci = qi::no_case[_p];
      
          return _b ? pcs : pci;
      }
      

    That way you don't repeatedly pay runtime cost.

  • In fact creating an entire grammar&lt;&gt; around a single rule just adds overhead and no features. I'd do away with the grammar (which adds debugging transparency by not going through a rule named "m_sStart". Instead you can name it &quot;booleanValue&quot; or something

  • Prefer qi::symbols&lt;char, bool&gt;; it will be more efficient and less error prone (e.g. when several options in the mapping can share prefixes)

  • If you don't want that, consider qi::attr(x) instead of semantic-action [ _val = x ]. This is particularly nifty with qi::match

I think that's about it for now.

huangapple
  • 本文由 发表于 2023年6月29日 00:41:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76575174.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定