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.
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.
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.
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?
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:
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.
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";
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With