【发布时间】:2022-12-01 19:10:35
【问题描述】:
I had recently with the help of the amazing sehe managed to advance my boost spirit x3 parser for hlsl (high level shading language) that is a c-like language for writing shader kernels for GPU's. Here is the rough grammar I am following... https://craftinginterpreters.com/appendix-i.html
Here is the previous question and answer for the curious.
Trying to parse nested expressions with boost spirit x3
I am now trying to implement unary and binary operators and have hit a stumbling block with how they recurse. I am able to get it to compile and a single binary operator is parsed, but having multiple nested ones doesn't seem to be working. I suspect the solution is going to be involving semantic actions again to manually propagate values but I struggle to see how to do that yet as the side effects are hard to understand (still working out how it all works).
Here's my compiling example...
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/spirit/home/x3/support/utility/error_reporting.hpp>
#include <iomanip>
#include <iostream>
namespace x3 = boost::spirit::x3;
namespace hlsl
{
namespace ast
{
struct Void
{
};
struct Get;
struct Set;
struct Call;
struct Assign;
struct Binary;
struct Unary;
struct Variable
{
std::string name;
};
using Expr = x3::variant<Void, x3::forward_ast<Get>, x3::forward_ast<Set>, Variable, x3::forward_ast<Call>, x3::forward_ast<Assign>, x3::forward_ast<Binary>, x3::forward_ast<Unary>>;
struct Call
{
Expr name;
std::vector<Expr> arguments_;
};
struct Get
{
Expr object_;
std::string property_;
};
struct Set
{
Expr object_;
Expr value_;
std::string name_;
};
struct Assign
{
std::string name_;
Expr value_;
};
struct Binary
{
Expr left_;
std::string op_;
Expr right_;
};
struct Unary
{
std::string op_;
Expr expr_;
};
} // namespace ast
struct printer
{
std::ostream &_os;
using result_type = void;
void operator()(hlsl::ast::Get const &get) const
{
_os << "get { object_:";
get.object_.apply_visitor(*this);
_os << ", property_:" << quoted(get.property_) << " }";
}
void operator()(hlsl::ast::Set const &set) const
{
_os << "set { object_:";
set.object_.apply_visitor(*this);
_os << ", name_:" << quoted(set.name_);
_os << " equals: ";
set.value_.apply_visitor(*this);
_os << " }";
}
void operator()(hlsl::ast::Assign const &assign) const
{
_os << "assign { ";
_os << "name_:" << quoted(assign.name_);
_os << ", value_:";
assign.value_.apply_visitor(*this);
_os << " }";
}
void operator()(hlsl::ast::Variable const &var) const
{
_os << "var{" << quoted(var.name) << "}";
};
void operator()(hlsl::ast::Binary const &bin) const
{
_os << "binary { ";
bin.left_.apply_visitor(*this);
_os << " " << quoted(bin.op_) << " ";
bin.right_.apply_visitor(*this);
_os << " }";
};
void operator()(hlsl::ast::Unary const &un) const
{
_os << "unary { ";
un.expr_.apply_visitor(*this);
_os << quoted(un.op_);
_os << " }";
};
void operator()(hlsl::ast::Call const &call) const
{
_os << "call{";
call.name.apply_visitor(*this);
_os << ", args: ";
for (auto &arg : call.arguments_)
{
arg.apply_visitor(*this);
_os << ", ";
}
_os << /*quoted(call.name) << */ "}";
};
void operator()(hlsl::ast::Void const &) const { _os << "void{}"; };
};
} // namespace hlsl
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Variable, name)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Call, name, arguments_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Get, object_, property_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Set, object_, value_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Assign, name_, value_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Binary, left_, op_, right_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Unary, op_, expr_)
namespace hlsl::parser
{
struct eh_tag;
struct error_handler
{
template <typename It, typename Exc, typename Ctx>
auto on_error(It &, It, Exc const &x, Ctx const &context) const
{
x3::get<eh_tag>(context)( //
x.where(), "Error! Expecting: " + x.which() + " here:");
return x3::error_handler_result::fail;
}
};
struct program_ : error_handler
{
};
x3::rule<struct identifier_, std::string> const identifier{"identifier"};
x3::rule<struct variable_, ast::Variable> const variable{"variable"};
x3::rule<struct arguments_, std::vector<ast::Expr>> const arguments{"arguments_"};
x3::rule<struct binary_, hlsl::ast::Binary, true> const binary{"binary"};
x3::rule<struct unary_, hlsl::ast::Unary> const unary{"unary"};
x3::rule<struct unarycallwrapper_, hlsl::ast::Expr> const unarycallwrapper{"unarycallwrapper"};
x3::rule<struct get_, ast::Expr> const get{"get"};
x3::rule<struct call_, ast::Expr> const call{"call"};
x3::rule<struct program_, ast::Expr> const program{"program"};
x3::rule<struct primary_, ast::Expr> const primary{"primary"};
x3::rule<struct expression_, ast::Expr> const expression{"expression"};
x3::rule<struct set_, ast::Set, true> const set{"set"};
x3::rule<struct assign_, ast::Assign> const assign{"assign"};
x3::rule<struct assignment_, ast::Expr> const assignment{"assignment"};
auto get_string_from_variable = [](auto &ctx)
{ _val(ctx).name_ = std::move(_attr(ctx).name); };
auto fix_assignExpr = [](auto &ctx)
{ _val(ctx).value_ = std::move(_attr(ctx)); };
auto as_expr = [](auto &ctx)
{ _val(ctx) = ast::Expr(std::move(_attr(ctx))); };
auto as_unary = [](auto &ctx)
{ _val(ctx) = ast::Unary(std::move(_attr(ctx))); };
auto as_call = [](auto &ctx)
{ _val(ctx) = ast::Call{std::move(_val(ctx)), std::move(_attr(ctx))}; };
auto fold_in_get_to_set = [](auto &ctx)
{
auto &val = x3::_val(ctx);
val.name_ = boost::get<x3::forward_ast<ast::Get>>(val.object_).get().property_;
val.object_ = ast::Expr(boost::get<x3::forward_ast<ast::Get>>(val.object_).get().object_);
};
auto as_string = [](auto &ctx)
{ _val(ctx) = std::move(_attr(ctx).name); };
auto as_assign = [](auto &ctx)
{ _val(ctx) = ast::Assign(std::move(_val(ctx)), std::move(_attr(ctx))); };
auto as_get = [](auto &ctx)
{
_val(ctx) = ast::Get{std::move(_val(ctx)), _attr(ctx)};
};
auto variable_def = identifier;
auto primary_def = variable;
auto identifier_def = x3::lexeme[x3::alpha >> *x3::alnum];
auto expression_def = assignment;
auto assignment_def = (assign | set) | binary; // replace binary with call to see the rest working
auto assign_def = variable[get_string_from_variable] >> '=' >> assignment[fix_assignExpr];
auto set_def = (get >> '=' >> assignment)[fold_in_get_to_set];
auto arguments_def = *(expression % ',');
auto get_def = primary[as_expr] >> *('.' >> identifier)[as_get];
auto call_def = primary[as_expr] >> *((x3::lit('(') >> arguments >> x3::lit(')'))[as_call] | ('.' >> identifier)[as_get]);
auto unary_def = (x3::string("-") >> unary);
auto unarycallwrapper_def = unary | call ;
auto binary_def = unarycallwrapper >> x3::string("*") >> unarycallwrapper;
auto program_def = x3::skip(x3::space)[expression];
BOOST_SPIRIT_DEFINE(primary, assign, binary, unary, unarycallwrapper, assignment, get, set, variable, arguments, expression, call, identifier, program);
} // namespace hlsl::parser
int main()
{
using namespace hlsl;
for (std::string const input :
{
"first",
"first.second",
"first.Second.third",
"first.Second().third",
"first.Second(arg1).third",
"first.Second(arg1, arg2).third",
"first = second",
"first.second = third",
"first.second.third = fourth",
"first.second.third = fourth()",
"first.second.third = fourth(arg1)",
"this * that", //binary { var{"this"} "*" var{"that"} }
"this * -that", // binary { var{"this"} "*" unary{'-', var{"that"}} }
"this * that * there",
}) //
{
std::cout << "===== " << quoted(input) << "\n";
auto f = input.begin(), l = input.end();
// Our error handler
auto const p = x3::with<parser::eh_tag>(
x3::error_handler{f, l, std::cerr})[hlsl::parser::program];
if (hlsl::ast::Expr fs; parse(f, l, p, fs))
{
fs.apply_visitor(hlsl::printer{std::cout << "Parsed: "});
std::cout << "\n";
}
else
{
std::cout << "Parse failed at " << quoted(std::string(f, l)) << "\n";
}
}
}
Any help is appreciated :)
【问题讨论】:
-
I was able to get the unary in the "this * -that" case to work by changing the...
auto unary_def = (x3::string("-") >> unary);toauto unary_def = (x3::string("-") >> unarycallwrapper);I'm also aware now that the first of the 3 terms in the binary parser should be evaluated to a unary and returned as an expression of the second two terms fail. I'm not sure how to get the 2nd and third term in the binary parser to be optional and still be synthesized into the Binary Attriubute on success. again I suspect some semantic action magic. Oh @sehe please save me :) -
The other thing I am thinking is that I probably don't need to store the binary op string as a string and could just use a lit and encode it into separate div and mult binary types. Then I could use the same semantic action machinery shown in the previous answer to nest multiple binary expressions. but there might be another way too.
-
Yeah semantic action magic is required to stay close to the grammar productions
标签: c++ boost boost-spirit boost-spirit-x3