Boost:spirit Parsing into structure and reusing parts of it

对着背影说爱祢 提交于 2021-02-19 07:38:23

问题


I have to find variables in a sentence and replace them by their value. Variables can be written in different forms, like $varName, or $(varName) for example.

I'd like to have a struct VariableHolder to have easy access to both :

struct VariableHolder
{
    string name;    // contains "varName"
    string fromFile;  // contains "$(varName)" or "$varName"
    void setName(ustring n) { name = n; }
}

Obviously, I'd like to avoid doing multiple passes and calling multiple parsers. What I have so far is this :

BOOST_FUSION_ADAPT_STRUCT(VariableHolder,
(ustring, fromFile)
)
// variableName is another parser that returns a string
qi::rule<Iterator, VariableHolder()> variable %=
                (qi::char_("$")
                    >> (variableName[phoenix::bind(&VariableHolder::setName, qi::_val, qi::_1)]
                        | (qi::char_("(")
                            >> variableName[phoenix::bind(&VariableHolder::setName, qi::_val, qi::_1)]
                            >> qi::char_(")")))
                    );

Which doesn't work. The name is correctly set, but the fromFile variable only contains a "$", and never anything else.

So, my questions to you fine folk :

  1. My idea was to only adapt part of the structure with BOOST_FUSION_ADAPT_STRUCT, and fill the rest with semantic actions. Stupid idea, or am I just doing it wrong?
  2. Is there a way to bind a semantic action and still get the output? Like

    char_[doSomething] // Can this both call doSomething, and parse a char?


回答1:


Addressing the questions:

  1. My idea was to only adapt part of the structure with BOOST_FUSION_ADAPT_STRUCT, and fill the rest with semantic actions. Stupid idea, or am I just doing it wrong?

Not unthinkable. Not my recommendation (see Boost Spirit: "Semantic actions are evil"?). But yeah you're doing it wrong:

You want lit("$") instead of char_("$") if you don't want it to be part of the exposed attribute. In fact, '$' will do here

  1. Is there a way to bind a semantic action and still get the output? Like

    char_[doSomething] // Can this both call doSomething, and parse a char?
    

Yes. You're doing it right now, and it works because of operator%= you used instead of operator= (see the docs: http://www.boost.org/doc/libs/1_61_0/libs/spirit/doc/html/spirit/qi/reference/nonterminal/rule.html#spirit.qi.reference.nonterminal.rule.expression_semantics).

However, it seems you are really trying to use the same input twice (raw as fromFile and "cooked" as name?) it backfires, because the auto-rule attribute propagation also overwrites name with the value you want for fromFile.

The only quick way out, here, is to use SA only. I'd suggest making VariableHolder's constructor responsible for details though.

Side note: it looks a bit as if the optional parentheses suggest an expression grammar. If so, make that explicit in the grammar, instead of hardcoding a special case in the rule for a variableName. If not, carry on :)

Here's an attempted fix:

Live On Coliru

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi      = boost::spirit::qi;
namespace phoenix = boost::phoenix;

struct VariableHolder {
    std::string name;     // contains "varName"
    std::string fromFile; // contains "$(varName)" or "$varName"
};

template <typename It, typename Skipper = qi::ascii::space_type> struct P : qi::grammar<It, VariableHolder(), Skipper> {
    P() : P::base_type(start) {
        auto _name     = phoenix::bind(&VariableHolder::name, qi::_val);
        auto _fromFile = phoenix::bind(&VariableHolder::fromFile, qi::_val);

        variableName = qi::alpha >> +qi::alnum;
        variable = '$' >> (variableName | '(' >> variableName >> ')');

        start = qi::as_string [ qi::raw [ 
                variable [ _name = qi::_1 ]] 
            ] [ _fromFile = qi::_1 ];

        BOOST_SPIRIT_DEBUG_NODES((start)(variable)(variableName))
    }

  private:
    qi::rule<It, std::string(), Skipper> variable;
    qi::rule<It, VariableHolder(), Skipper> start;
    // lexemes
    qi::rule<It, std::string()> variableName;
};

int main() {
    using It = std::string::const_iterator;
    P<It> const p;

    for (std::string const input : { 
            "$foo1",
            "$(bar2)" 
        }) 
    {
        It f = input.begin(), l = input.end();
        VariableHolder data;

        bool ok = qi::phrase_parse(f, l, p, qi::ascii::space, data);

        if (ok) {
            std::cout << "Parse success: " << data.name << " (source: '" << data.fromFile << "')\n";
        } else {
            std::cout << "Parse failure ('" << input << "')\n";
        }

        if (f != l) {
            std::cout << "Remaining unparsed input: '" << std::string(f, l) << "'\n";
        }
    }
}

Prints

Parse success: foo1 (source: '$foo1')
Parse success: bar2 (source: '$(bar2)')


来源:https://stackoverflow.com/questions/38877702/boostspirit-parsing-into-structure-and-reusing-parts-of-it

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!