Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing preconditions and recursion with Boost::Spirit

I am trying to parse a PDDL file with Boost::Spirit and have some trouble with parsing preconditions into a struct. I am struggling to understand the Boost manual on how to get the condition into my struct and with recursion.

I give a snippet of the code below, that should illustrate the problem well. A string that looks like this has to be parsed:

:precondition
(and
  (at-pos ?r ?pos)
  (not (has-pos ?m ?pos))
)

My code so far looks like this, but I am almost sure I did not understand how at_c works having no experience with Boost::Phoenix yet.

predi_param = '?' >> name_type;
predi = '(' 
    >> name_type
    >> +predi_param
    >> ')';
literal = ( 
    ( '(' >> lit("not") >>
      predi       [at_c<0>(_val) = false]
      >> ')'
    )
    | predi       [at_c<0>(_val) = true]
  )
  >> ')';
pred_list = ( '(' >> lit("and") >> (*pred_list) >> ')')
  | literal;
preconditions = lit(":precondition") >> pred_list;

qi::rule<Iterator, std::string(), ascii::space_type> predi_param;
qi::rule<Iterator, Predicate(), ascii::space_type> predi;
qi::rule<Iterator, Literal(), ascii::space_type> literal;
qi::rule<Iterator, std::vector<Literal>(), ascii::space_type> preconditions, pred_list;

My AST looks like this:

struct Predicate
{
  std::string name;
  std::vector<std::string> predicate_params;
};  
struct Literal
{
  bool condition;
  Predicate predicate;
};

BOOST_FUSION_ADAPT_STRUCT(
    pddl_parser::Literal,
    (bool, condition)
    (pddl_parser::Predicate, predicate)
)

BOOST_FUSION_ADAPT_STRUCT(
    pddl_parser::Predicate,
    (std::string, name)
    (std::vector<std::string>, predicate_params)
)

Compiling this results in a compilation error:

parser.cpp:67:17:   required from ‘pddl_parser::domain_parser<Iterator>::domain_parser() [with Iterator = __gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >]’
parser.cpp:136:10:   required from here
/usr/include/boost/spirit/home/qi/detail/assign_to.hpp:153:20: error: no matching function for call to ‘pddl_parser::Literal::Literal(const std::vector<pddl_parser::Literal>&)’
             attr = static_cast<Attribute>(val);
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from parser.cpp:11:0:
./pddlast.h:23:10: note: candidate: pddl_parser::Literal::Literal()
   struct Literal
          ^~~~~~~
./pddlast.h:23:10: note:   candidate expects 0 arguments, 1 provided
./pddlast.h:23:10: note: candidate: pddl_parser::Literal::Literal(const pddl_parser::Literal&)
./pddlast.h:23:10: note:   no known conversion for argument 1 from ‘const std::vector<pddl_parser::Literal>’ to ‘const pddl_parser::Literal&’
./pddlast.h:23:10: note: candidate: pddl_parser::Literal::Literal(pddl_parser::Literal&&)
./pddlast.h:23:10: note:   no known conversion for argument 1 from ‘const std::vector<pddl_parser::Literal>’ to ‘pddl_parser::Literal&&’

If I reformat the pred_list for test purposes into pred_list = ( '(' >> *literal) >> ')'); the code compiles but still yields no success, although I take the (and ) out. I have the impression I have something totally wrong, but cannot find anything. This is my first try using Boost::Spirit.

like image 784
Raptor Avatar asked Oct 18 '22 12:10

Raptor


1 Answers

Well, you say that

pred_list = ( '(' >> *literal) >> ')');

compiles, but the following does not:

pred_list = ( '(' >> lit("and") >> (*pred_list) >> ')') | literal;

If you look closely, that makes sense. Since pred_list has a declared attribute type of std::vector<Literal>, obviously repeated literals (*literal) might be compatible with that for automatic attribute propagation.

Now, look at the second rule definition. It parses a bunch of attribute-less literals ('(', "and", ')') and then ... *pred_list. If pred_list declares a std::vector<Literal> attribute, surely *pred_list synthesizes a std::vector<std::vector<Literal> >. To make matters worse, the "after-thought" | literal makes the synthesized attribute equivalent of a variant<vector<vector<Literal>>, Literal>.

Yes. That's a bit of a mess. Your AST simply doesn't reflect the rules, or vice versa.

The Road Ahead

You should probably restate your problem, removing the failed implementation bits and describing the goal instead. If we can know the true grammatic requirements, /then/ we can induce a matching AST that is correct for it.

Interlude

During the intermission, let me simplify that rule for literal. (See Boost spirit semantic actions on qi::rule for a backgrounder):

literal = 
    '(' >> matches("not") >> predi >> ')'
    | qi::attr(false) >> predi
    ;

PS It appears that a stray cat also typed an extra unbalanced

    >> ')';

at the end there?

A constructive guess

From just looking at the sample input, I wager you just want to parse scheme-like function applications of the form

(function_name arguments)

Where the applications can nest. So, arguments are either atoms or function applications.

Well, let's put an AST on it, quickly:

namespace AST {
    using Atom = std::string;

    struct Application;

    using Expression = boost::variant<Atom, Application>;

    struct Application {
        Atom function;
        std::vector<Expression> arguments;
    };
}

That's delightfully simple, right. And here's the simplest grammar that will parse your precondition:

template <typename Iterator>
struct Precondition : qi::grammar<Iterator, AST::Expression()> {
    Precondition() : Precondition::base_type(precondition) {
        using namespace qi;

        atom         = +(graph - '(' - ')');
        application  = '(' >> atom >> *expression >> ')';
        expression   = atom | application;

        precondition = skip(ascii::space) [":precondition" >> expression];

        BOOST_SPIRIT_DEBUG_NODES((precondition)(expression)(application)(atom))
    }

  private:
    using Skipper = qi::ascii::space_type;
    qi::rule<Iterator, AST::Application(), Skipper> application;
    qi::rule<Iterator, AST::Expression(), Skipper>  expression;

    // lexemes
    qi::rule<Iterator, AST::Expression()> precondition;
    qi::rule<Iterator, AST::Atom()> atom;
};

Note how each of the rules merely restates the corresponding AST node. precondition also hides the skipper from the outside world.

The output: Live On Coliru

Prints

Parsed (and (at-pos ?r ?pos) (not (has-pos ?m ?pos)))

And with BOOST_SPIRIT_DEBUG enabled:

<precondition>
  <try>:precondition\n      </try>
  <expression>
    <try>\n                   </try>
    <atom>
      <try>(and\n               </try>
      <fail/>
    </atom>
    <application>
      <try>(and\n               </try>
      <atom>
        <try>and\n                </try>
        <success>\n                   </success>
        <attributes>[[a, n, d]]</attributes>
      </atom>
      <expression>
        <try>\n                   </try>
        <atom>
          <try>(at-pos ?r ?pos)\n   </try>
          <fail/>
        </atom>
        <application>
          <try>(at-pos ?r ?pos)\n   </try>
          <atom>
            <try>at-pos ?r ?pos)\n    </try>
            <success> ?r ?pos)\n          </success>
            <attributes>[[a, t, -, p, o, s]]</attributes>
          </atom>
          <expression>
            <try> ?r ?pos)\n          </try>
            <atom>
              <try>?r ?pos)\n           </try>
              <success> ?pos)\n             </success>
              <attributes>[[?, r]]</attributes>
            </atom>
            <success> ?pos)\n             </success>
            <attributes>[[?, r]]</attributes>
          </expression>
          <expression>
            <try> ?pos)\n             </try>
            <atom>
              <try>?pos)\n              </try>
              <success>)\n                  </success>
              <attributes>[[?, p, o, s]]</attributes>
            </atom>
            <success>)\n                  </success>
            <attributes>[[?, p, o, s]]</attributes>
          </expression>
          <expression>
            <try>)\n                  </try>
            <atom>
              <try>)\n                  </try>
              <fail/>
            </atom>
            <application>
              <try>)\n                  </try>
              <fail/>
            </application>
            <fail/>
          </expression>
          <success>\n                   </success>
          <attributes>[[[a, t, -, p, o, s], [[?, r], [?, p, o, s]]]]</attributes>
        </application>
        <success>\n                   </success>
        <attributes>[[[a, t, -, p, o, s], [[?, r], [?, p, o, s]]]]</attributes>
      </expression>
      <expression>
        <try>\n                   </try>
        <atom>
          <try>(not (has-pos ?m ?po</try>
          <fail/>
        </atom>
        <application>
          <try>(not (has-pos ?m ?po</try>
          <atom>
            <try>not (has-pos ?m ?pos</try>
            <success> (has-pos ?m ?pos))\n</success>
            <attributes>[[n, o, t]]</attributes>
          </atom>
          <expression>
            <try> (has-pos ?m ?pos))\n</try>
            <atom>
              <try>(has-pos ?m ?pos))\n </try>
              <fail/>
            </atom>
            <application>
              <try>(has-pos ?m ?pos))\n </try>
              <atom>
                <try>has-pos ?m ?pos))\n  </try>
                <success> ?m ?pos))\n         </success>
                <attributes>[[h, a, s, -, p, o, s]]</attributes>
              </atom>
              <expression>
                <try> ?m ?pos))\n         </try>
                <atom>
                  <try>?m ?pos))\n          </try>
                  <success> ?pos))\n            </success>
                  <attributes>[[?, m]]</attributes>
                </atom>
                <success> ?pos))\n            </success>
                <attributes>[[?, m]]</attributes>
              </expression>
              <expression>
                <try> ?pos))\n            </try>
                <atom>
                  <try>?pos))\n             </try>
                  <success>))\n                 </success>
                  <attributes>[[?, p, o, s]]</attributes>
                </atom>
                <success>))\n                 </success>
                <attributes>[[?, p, o, s]]</attributes>
              </expression>
              <expression>
                <try>))\n                 </try>
                <atom>
                  <try>))\n                 </try>
                  <fail/>
                </atom>
                <application>
                  <try>))\n                 </try>
                  <fail/>
                </application>
                <fail/>
              </expression>
              <success>)\n                  </success>
              <attributes>[[[h, a, s, -, p, o, s], [[?, m], [?, p, o, s]]]]</attributes>
            </application>
            <success>)\n                  </success>
            <attributes>[[[h, a, s, -, p, o, s], [[?, m], [?, p, o, s]]]]</attributes>
          </expression>
          <expression>
            <try>)\n                  </try>
            <atom>
              <try>)\n                  </try>
              <fail/>
            </atom>
            <application>
              <try>)\n                  </try>
              <fail/>
            </application>
            <fail/>
          </expression>
          <success>\n                   </success>
          <attributes>[[[n, o, t], [[[h, a, s, -, p, o, s], [[?, m], [?, p, o, s]]]]]]</attributes>
        </application>
        <success>\n                   </success>
        <attributes>[[[n, o, t], [[[h, a, s, -, p, o, s], [[?, m], [?, p, o, s]]]]]]</attributes>
      </expression>
      <expression>
        <try>\n                   </try>
        <atom>
          <try>)</try>
          <fail/>
        </atom>
        <application>
          <try>)</try>
          <fail/>
        </application>
        <fail/>
      </expression>
      <success></success>
      <attributes>[[[a, n, d], [[[a, t, -, p, o, s], [[?, r], [?, p, o, s]]], [[n, o, t], [[[h, a, s, -, p, o, s], [[?, m], [?, p, o, s]]]]]]]]</attributes>
    </application>
    <success></success>
    <attributes>[[[a, n, d], [[[a, t, -, p, o, s], [[?, r], [?, p, o, s]]], [[n, o, t], [[[h, a, s, -, p, o, s], [[?, m], [?, p, o, s]]]]]]]]</attributes>
  </expression>
  <success></success>
  <attributes>[[[a, n, d], [[[a, t, -, p, o, s], [[?, r], [?, p, o, s]]], [[n, o, t], [[[h, a, s, -, p, o, s], [[?, m], [?, p, o, s]]]]]]]]</attributes>
</precondition>

What this example shows you is:

  • how to represent the variant data-type in your AST
  • how to achieve recursion through it (see also documentation for more advanced information)

What it doesn't immediately do is to validate the AST against the PDDL specs. I'm not at all sure how much of it you intend to implement, so I think the more generic starter might be helpful anyways.

Full Listing

Live On Coliru

#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace AST {
    using Atom = std::string;

    struct Application;

    using Expression = boost::variant<Atom, Application>;

    struct Application {
        Atom function;
        std::vector<Expression> arguments;

        friend std::ostream& operator<<(std::ostream& os, Application const& a) {
            os << "(" << a.function;
            for (auto& arg : a.arguments)
                os << " " << arg;
            return os << ")";
        }
    };
}

BOOST_FUSION_ADAPT_STRUCT(AST::Application, function, arguments)

namespace pddl_parser {

    namespace qi    = boost::spirit::qi;

    template <typename Iterator>
    struct Precondition : qi::grammar<Iterator, AST::Expression()> {
        Precondition() : Precondition::base_type(precondition) {
            using namespace qi;

            atom         = +(graph - '(' - ')');
            application  = '(' >> atom >> *expression >> ')';
            expression   = atom | application;

            precondition = skip(ascii::space) [":precondition" >> expression];

            BOOST_SPIRIT_DEBUG_NODES((precondition)(expression)(application)(atom))
        }

      private:
        using Skipper = qi::ascii::space_type;
        qi::rule<Iterator, AST::Application(), Skipper> application;
        qi::rule<Iterator, AST::Expression(), Skipper>  expression;

        // lexemes
        qi::rule<Iterator, AST::Expression()> precondition;
        qi::rule<Iterator, AST::Atom()> atom;
    };
}

int main() {
    using It = std::string::const_iterator;

    for (std::string const& input : {
            R"--(:precondition
                    (and
                      (at-pos ?r ?pos)
                      (not (has-pos ?m ?pos))
                    ))--"
            })
    {
        std::cout << "-----\n";
        It f = input.begin(), l = input.end();

        AST::Expression precondition;
        bool ok = parse(f, l, pddl_parser::Precondition<It>{}, precondition);

        if (ok) {
            std::cout << "Parsed " << precondition << "\n";
        } else {
            std::cout << "Parse Failed\n";
        }

        if (f != l) {
            std::cout << "Remaining unparsed input: '" << std::string(f, l) << "'\n";
        }
    }
}
like image 179
sehe Avatar answered Oct 20 '22 22:10

sehe