Trying to parse nested expressions with boost spirit x3 - c++

My ultimate goal is to write a hlsl shading language parser. My first experience with parsing has been by following bob nystrom's "crafting interpreters".
The issue I am currently facing is that I am trying to parse a 'chained member access' sequence (or multiple 'dot operators)....
first.Second.third
Obviously I could parse that into a list % sequence as a vector of strings, but I am trying to stick to the ast shown in the crafting interpreters book by having nested 'Get' nodes.
I am trying to parse this nested Get sequence so that I can eventually put that into a Set ast node. But I thought it would be best to at least get the 'Get' part first. before building on top of that.
https://craftinginterpreters.com/classes.html#set-expressions
Here's my minimal compiling program that tries to do that....
#include "boost/variant.hpp"
#include <boost/config/warning_disable.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/fusion/include/std_tuple.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/position_tagged.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/spirit/home/x3/support/utility/annotate_on_success.hpp>
#include <boost/spirit/home/x3/support/utility/error_reporting.hpp>
#include <iostream>
#include <string>
#include <tuple>
#include <variant>
namespace hlsl {
namespace ast {
struct Get;
struct ExprVoidType {};
struct Variable {
Variable(std::string name) : name(std::move(name)) {
}
Variable() = default;
std::string name;
};
using Expr =
boost::spirit::x3::variant<ExprVoidType,
boost::spirit::x3::forward_ast<Get>, Variable>;
struct Get {
Get(Expr& object, std::string name) : object_{object}, name_{name} {
}
Get() = default;
Expr object_;
std::string name_;
};
} // namespace ast
} // namespace hlsl
struct visitor {
using result_type = void;
void operator()(const std::string name) {
std::cout << name << "\n";
}
void operator()(const hlsl::ast::Get& get) {
std::cout << "get expr\n";
get.object_.apply_visitor(*this);
std::cout << get.name_ << "\n";
}
void operator()(const hlsl::ast::Variable& var) {
std::cout << var.name << "\n";
};
void operator()(const hlsl::ast::ExprVoidType& var){};
};
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Variable, name)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Get, object_, name_)
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
using ascii::char_;
using ascii::space;
using x3::alnum;
using x3::alpha;
using x3::double_;
using x3::int_;
using x3::lexeme;
using x3::lit;
struct error_handler {
template <typename Iterator, typename Exception, typename Context>
x3::error_handler_result on_error(Iterator& first, Iterator const& last,
Exception const& x,
Context const& context) {
auto& error_handler = x3::get<x3::error_handler_tag>(context).get();
std::string message = "Error! Expecting: " + x.which() + " here:";
error_handler(x.where(), message);
return x3::error_handler_result::fail;
}
};
/////////////////////////////////////////
// RULES
///////////////////////////////////////////
x3::rule<class identifier_class, std::string> const identifier = "identifier";
auto const identifier_def = +alnum;
BOOST_SPIRIT_DEFINE(identifier);
x3::rule<class expression_class, hlsl::ast::Expr> const expression =
"expression";
x3::rule<class variable_class, hlsl::ast::Variable> const variable = "variable";
x3::rule<class get_class, hlsl::ast::Get> const get = "get";
auto const variable_def = identifier;
BOOST_SPIRIT_DEFINE(variable);
auto const expression_def = get | variable;
BOOST_SPIRIT_DEFINE(expression);
///////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
// get
auto const get_def = (variable | expression) >> '.' >> identifier;
BOOST_SPIRIT_DEFINE(get);
/////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
struct program_class;
x3::rule<program_class, hlsl::ast::Expr> const program = "program";
auto const program_def = get;
BOOST_SPIRIT_DEFINE(program);
struct program_class : error_handler {};
// struct program_class;
/////////////////////////////////////////////////////////
// } // namespace parser
// } // namespace client
////////////////////////////////////////////////////////////////////////////
// Main program
////////////////////////////////////////////////////////////////////////////
int main() {
using boost::spirit::x3::error_handler_tag;
using boost::spirit::x3::with;
using iterator_type = std::string::const_iterator;
using error_handler_type = boost::spirit::x3::error_handler<iterator_type>;
// input string
std::string input = "first.Second.third";
hlsl::ast::Expr fs;
auto iter = input.begin();
auto const end = input.end();
// Our error handler
error_handler_type error_handler(iter, end, std::cerr);
auto const parser =
// we pass our error handler to the parser so we can access
// it later in our on_error and on_sucess handlers
with<error_handler_tag>(std::ref(error_handler))[program];
bool r;
r = phrase_parse(iter, end, parser, space, fs);
visitor v;
if (r) {
std::cout << "Parse Suceeded\n\n";
fs.apply_visitor(v);
} else {
std::cout << "Sorry :(\n\n";
std::cout << *iter;
}
std::cout << "Bye... :-) \n\n";
return 0;
}
What I want is something like this
Get {
object_: Get {
object_: Variable {
name : "first"
},
name_: second
},
name_: third
}
Is this kind of thing even possible using x3 and the way it constructs parsers from grammar?

Sure. Your grammar parses left-to right, and that's also how you want to build your ast (outside-in, not inside out).
I'd rephrase the whole thing:
expression = variable >> *('.' >> identifier);
Now you'll have to massage the attribute propagation as each . member access wraps the previous expression in another Get{expression, name} instance:
x3::rule<struct identifier_, std::string> const identifier{"identifier"};
x3::rule<struct variable_, ast::Variable> const variable{"variable"};
x3::rule<struct expression_, ast::Expr> const expression{"expression"};
x3::rule<struct program_, ast::Expr> const program{"program"};
auto identifier_def = x3::lexeme[x3::alpha >> *x3::alnum];
auto variable_def = identifier;
Now let's use two semantic actions to propagate the expression parts:
auto as_expr = [](auto& ctx) { _val(ctx) = ast::Expr(std::move(_attr(ctx))); };
auto as_get = [](auto& ctx) {
_val(ctx) = ast::Get{std::move(_val(ctx)), _attr(ctx)};
};
auto expression_def = variable[as_expr] >> *('.' >> identifier[as_get]);
Let's also bake the skipper into the grammar while we're at it:
auto program_def = x3::skip(x3::space)[expression];
Live Demo
With a lot of simplifications, e.g. for the AST & visitor:
Live On Coliru
#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 Variable {
std::string name;
};
using Expr = x3::variant<Void, x3::forward_ast<Get>, Variable>;
struct Get {
Expr object_;
std::string property_;
};
} // 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::Variable const& var) const {
_os << "var{" << quoted(var.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::Get, object_, property_)
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 expression_, ast::Expr> const expression{"expression"};
x3::rule<struct program_, ast::Expr> const program{"program"};
auto as_expr = [](auto& ctx) { _val(ctx) = ast::Expr(std::move(_attr(ctx))); };
auto as_get = [](auto& ctx) {
_val(ctx) = ast::Get{std::move(_val(ctx)), _attr(ctx)};
};
auto identifier_def = x3::lexeme[x3::alpha >> *x3::alnum];
auto variable_def = identifier;
auto expression_def = variable[as_expr] >> *('.' >> identifier)[as_get];
auto program_def = x3::skip(x3::space)[expression];
BOOST_SPIRIT_DEFINE(variable, expression, identifier, program);
} // namespace hlsl::parser
int main() {
using namespace hlsl;
for (std::string const input :
{
"first",
"first.second",
"first.Second.third",
}) //
{
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_view(f, l)) << "\n";
}
}
}
Prints
===== "first"
Parsed: var{"first"}
===== "first.second"
Parsed: get { object_:var{"first"}, property_:"second" }
===== "first.Second.third"
Parsed: get { object_:get { object_:var{"first"}, property_:"Second" }, property_:"third" }
More Simplifications
In the current scenario none of the rules are recursive, so don't need the _DEFINE magic. Assuming you need recursion in the expression later, you could at least remove some redundancy:
namespace hlsl::parser {
x3::rule<struct expression_, ast::Expr> const expression{"expression"};
auto as_expr = [](auto& ctx) { _val(ctx) = ast::Expr(std::move(_attr(ctx))); };
auto as_get = [](auto& ctx) { _val(ctx) = ast::Get{std::move(_val(ctx)), _attr(ctx)}; };
auto identifier
= x3::rule<void, std::string>{"identifier"}
= x3::lexeme[x3::alpha >> *x3::alnum];
auto variable = x3::rule<void, ast::Variable>{"variable"} = identifier;
auto expression_def = variable[as_expr] >> *('.' >> identifier)[as_get];
auto program = x3::skip(x3::space)[expression];
BOOST_SPIRIT_DEFINE(expression)
} // namespace hlsl::parser
Note also that the lexeme is important to suppress skipping (Boost spirit skipper issues)
See it Live On Coliru as well.
Oh and for bonus, a version without x3::variant or visitation:
Live On Coliru
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iomanip>
#include <iostream>
namespace x3 = boost::spirit::x3;
namespace hlsl::ast {
struct Void {};
struct Get;
struct Variable {
std::string name;
};
using Expr = boost::variant<Void, boost::recursive_wrapper<Get>, Variable>;
struct Get {
Expr object_;
std::string property_;
};
static inline std::ostream& operator<<(std::ostream& os, Void) {
return os << "void()";
}
static inline std::ostream& operator<<(std::ostream& os, Variable const& v) {
return os << "var{" << std::quoted(v.name) << "}";
}
static inline std::ostream& operator<<(std::ostream& os, Get const& g) {
return os << "get{ object_:" << g.object_ << ", property_:" << quoted(g.property_)
<< " }";
}
} // namespace hlsl::ast
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Variable, name)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Get, object_, property_)
namespace hlsl::parser {
x3::rule<struct expression_, ast::Expr> const expression{"expression"};
auto as_expr = [](auto& ctx) { _val(ctx) = ast::Expr(std::move(_attr(ctx))); };
auto as_get = [](auto& ctx) { _val(ctx) = ast::Get{std::move(_val(ctx)), _attr(ctx)}; };
auto identifier
= x3::rule<void, std::string>{"identifier"}
= x3::lexeme[x3::alpha >> *x3::alnum];
auto variable = x3::rule<void, ast::Variable>{"variable"} = identifier;
auto expression_def = variable[as_expr] >> *('.' >> identifier)[as_get];
auto program = x3::skip(x3::space)[expression];
BOOST_SPIRIT_DEFINE(expression)
} // namespace hlsl::parser
int main() {
using namespace hlsl;
for (std::string const input :
{
"first",
"first.second",
"first.Second.third",
}) //
{
std::cout << "===== " << quoted(input) << "\n";
auto f = input.begin(), l = input.end();
if (ast::Expr fs; parse(f, l, parser::program, fs)) {
std::cout << "Parsed: " << fs << "\n";
} else {
std::cout << "Parse failed at " << quoted(std::string_view(f, l)) << "\n";
}
}
}
Prints just the same:
===== "first"
Parsed: var{"first"}
===== "first.second"
Parsed: get{ object_:var{"first"}, property_:"second" }
===== "first.Second.third"
Parsed: get{ object_:get{ object_:var{"first"}, property_:"Second" }, property_:"third" }
That's >100 lines of code removed. With no functionality sacrificed.

Related

How to propagate binary operators in boost spirit x3?

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 :)
You found out how to jump hoops already :)
To lend some perspective I started from scratch. I copied the specs as a markdown comment. I basically copy pasted stuff and mapped an AST 1:1:
namespace Ast {
//////////////////
// primitive types
struct Nil { };
struct Identifier : std::string { using std::string::string; };
struct String : std::string { using std::string::string; };
enum class Bool { False, True };
using Number = boost::multiprecision::cpp_dec_float_50;
//////////////////
// expressions
enum class Op {
Plus, Minus, Multiply, Divide,
Equal, NotEqual, NOT, OR, AND,
GT, GTE, LT, LTE,
Assign
};
#define FWD(T) boost::recursive_wrapper<struct T>
using boost::optional;
using boost::blank; // std::monostate
using boost::variant;
using Expression = variant< //
Nil, Bool, Number, Identifier, String, //
FWD(FunctionCall), //
FWD(MemberAccess), //
FWD(Unary), //
FWD(Binary) //
>;
using Parameters = std::vector<Identifier>;
using Arguments = std::vector<Expression>;
struct FunctionCall { Expression fun; Arguments args; };
struct MemberAccess { Expression obj; Identifier mem; };
struct Unary { Op op; Expression oper; };
struct Binary { Op op; Expression lhs, rhs; };
//////////////////
// Declarations
struct PrintStmt { Expression value; };
struct ReturnStmt { optional<Expression> value; };
using Statement = variant< //
Expression, PrintStmt, ReturnStmt,
FWD(ForStmt), //
FWD(IfStmt), //
FWD(WhileStmt), //
FWD(Block) //
>;
using Statements = std::vector<Statement>;
struct VarDecl {
Identifier id;
optional<Expression> init;
};
struct ForStmt {
variant<blank, VarDecl, Expression> init;
optional<Expression> cond, incr;
optional<Statement> body;
};
struct IfStmt {
Expression cond;
Statement branch1;
optional<Statement> branch2;
};
struct WhileStmt { // REVIEW might represent as ForStmt
Expression cond;
Statement body;
};
struct Block {
Statements stmts;
};
//////////////////
// Declarations
struct FunDecl {
Identifier id;
Parameters params;
Block body;
};
struct ClassDecl {
Identifier id;
optional<Identifier> super;
std::vector<FunDecl> funcs;
};
using Declaration = boost::variant<ClassDecl, FunDecl, VarDecl, Statement>;
using Declarations = std::vector<Declaration>;
using Program = Declarations;
} // namespace Ast
Notes:
I used decimal number representation to not have to deal with too many representation issues
I changed Block content to be statements instead of declarations. It's unlikely that the script should really allow local class declarations. Allowing it means effectively the Declaration and Statement variant have to merge.
Adapting as Fusion sequences:
BOOST_FUSION_ADAPT_STRUCT(Ast::PrintStmt, value)
BOOST_FUSION_ADAPT_STRUCT(Ast::ReturnStmt, value)
BOOST_FUSION_ADAPT_STRUCT(Ast::ForStmt, init, cond, incr, body)
BOOST_FUSION_ADAPT_STRUCT(Ast::IfStmt, cond, branch1, branch2)
BOOST_FUSION_ADAPT_STRUCT(Ast::WhileStmt, cond, body)
BOOST_FUSION_ADAPT_STRUCT(Ast::Block, stmts)
BOOST_FUSION_ADAPT_STRUCT(Ast::FunDecl, id, params, body)
BOOST_FUSION_ADAPT_STRUCT(Ast::ClassDecl, id, super, funcs)
BOOST_FUSION_ADAPT_STRUCT(Ast::VarDecl, id, init)
// These are not required because they're constructed from semantic actions
//BOOST_FUSION_ADAPT_STRUCT(Ast::Unary, op, oper)
//BOOST_FUSION_ADAPT_STRUCT(Ast::Binary, lhs, rhs)
//BOOST_FUSION_ADAPT_STRUCT(Ast::FunctionCall, fun, args)
//BOOST_FUSION_ADAPT_STRUCT(Ast::MemberAccess, obj, mem)
Next up we declare rules for anything that is gonna recurse:
x3::rule<struct declaration, Ast::Declaration> declaration {"declaration"};
x3::rule<struct statement, Ast::Statement> statement {"statement"};
x3::rule<struct expression, Ast::Expression> expression {"expression"};
x3::rule<struct call, Ast::Expression> call {"call"};
Sadly, due to the operator precedence levels being split up in separate grammar productions, we get a proliferation of these rules:
x3::rule<struct unary, Ast::Expression> unary {"unary"};
x3::rule<struct factor, Ast::Expression> factor {"factor"};
x3::rule<struct term, Ast::Expression> term {"term"};
x3::rule<struct comparison, Ast::Expression> comparison {"comparison"};
x3::rule<struct equality, Ast::Expression> equality {"equality"};
x3::rule<struct logic_and, Ast::Expression> logic_and {"logic_and"};
x3::rule<struct logic_or, Ast::Expression> logic_or {"logic_or"};
x3::rule<struct assignment, Ast::Expression> assignment {"assignment"};
The lexicals are simple enough:
auto number = AST(Number,
x3::raw[x3::lexeme[ //
+x3::digit >> -("." >> +x3::digit) //
]][to_number]);
auto alpha = x3::char_("a-zA-Z_");
auto alnum = x3::char_("a-zA-Z_0-9");
auto identifier = AST(Identifier, x3::lexeme[alpha >> *alnum]);
auto string = AST(String, x3::lexeme['"' >> *~x3::char_('"') >> '"']);
I see I forgot to introduce AST(T, p) macro in time. See below.
Constructing the decimal number from string is fine:
auto to_number = [](auto& ctx) {
auto& raw = _attr(ctx);
_val(ctx) = Ast::Number{std::string(raw.begin(), raw.end())};
};
Keyword Checking
As an advanced feature I added keyword checking. You will find out you need it when you have a function name starting with a keyword, e.g.
def for_each(container, action) {
for (var i = 0; i < = container.size(); ++i) {
action(container.item(i));
}
}
for_each would misparse for as the keyword, unless we check that it is not immediately followed by "identifier" characters. Let's also make this a configuration point for case sensitivity:
// keyword checking
#if CASE_SENSITIVE
auto cs(auto p) { return p; };
#else
auto cs(auto p) { return x3::no_case[p]; };
#endif
auto kw(auto... p) { return x3::lexeme[(cs(p) | ...) >> !alnum]; }
Now we can use kw("for") instead of "for" and it will be properly case sensitive and boundary-checked.
Reserved keywords
The specs don't say, but you may want to avoid creating variables with reserved names. E.g. (return)("key").index would be an expression that invokes a function named return, but return ("key") would be a statement that returns the expression "key" (wrapped in a redundant subexpression).
So, let's add some logic to distinguish non-reserved identifiers:
// utility
auto bool_ = [] {
x3::symbols<Ast::Bool> sym;
sym.add("true", Ast::Bool::True);
sym.add("false", Ast::Bool::False);
return kw(sym);
}();
// Not specified, use `non_reserved = identifier` to allow those
auto reserved = kw("return", bool_, "nil", "fun", "var", "class");
auto non_reserved = !reserved >> identifier;
AST Building
I think I mentioned the at<T>(p) device before.
template <typename T> auto as(auto p, char const* name) {
return x3::rule<struct _, T>{name} = std::move(p);
};
template <typename T> auto as(auto p) {
static auto const name = boost::core::demangle(typeid(T).name());
return as<T>(std::move(p), name.c_str());
};
Making it less verbose with Ast:: types:
#define AST(T, p) as<Ast::T>(p, #T)
Now the utility productions from the grammar can be written as:
auto parameters = AST(Parameters, -(non_reserved % ","));
auto block = AST(Block,"{" >> *statement >> "}");
auto function = AST(FunDecl, non_reserved >> "(" >> parameters >> ")" >> block);
Declarations
// declarations
auto classDecl = AST(ClassDecl, //
kw("class") >> non_reserved >> -("<" >> non_reserved) >> //
"{" >> *function >> "}" //
);
auto funDecl = kw("fun") >> function;
auto varDecl = kw("var") >> AST(VarDecl, non_reserved >> -("=" >> expression) >> ";");
auto declaration_def = AST(Declaration, classDecl | funDecl | varDecl | statement);
auto program = x3::skip(skipper)[AST(Program, *(!x3::eoi >> declaration)) >> x3::eoi];
Not a lot to be said, except note the embedding of the skipper. For fun and exposition, I've customized the skipper to allow C++ style comments:
auto comment //
= ("//" > *(x3::char_ - x3::eol) > (x3::eoi | x3::eol)) //
| ("/*" > *(x3::char_ - "*/") > "*/") //
; //
auto skipper = x3::space | comment;
Statements
It's a bit of tedium, but the Fusion adaptations and previously introduced kw(...) and AST(T, p) helpers do all the heavy lifting:
// statements
auto exprStmt = AST(Expression, expression >> ";");
auto forStmt = AST(ForStmt, //
kw("for") >> "(" >> //
(varDecl | exprStmt | ";") >> //
-expression >> ";" >> //
-expression >> ")" >> statement);
auto ifStmt = AST(IfStmt, //
kw("if") >> ("(" >> expression >> ")") >> statement >>
-(kw("else") >> statement));
auto printStmt = AST(PrintStmt, kw("print") >> expression >> ";");
auto returnStmt = AST(ReturnStmt, kw("return") >> -expression >> ";");
auto whileStmt = AST(WhileStmt, kw("while") >> "(" >> expression >> ")" >> statement);
auto statement_def = AST(Statement, !(x3::eoi | "}") //
>> (forStmt | ifStmt | printStmt | returnStmt |
whileStmt | block | exprStmt));
Note how these are basically carbon copies of the specs.
Expressions
Here is the part that gave trouble.
First let's get the simple things out of way:
auto opsym = [] {
x3::symbols<Ast::Op> sym;
sym.add //
("+", Ast::Op::Plus)("-", Ast::Op::Minus) //
("*", Ast::Op::Multiply)("/", Ast::Op::Divide) //
("==", Ast::Op::Equal)("!=", Ast::Op::NotEqual) //
("!", Ast::Op::NOT)("or", Ast::Op::OR)("and", Ast::Op::AND) //
(">", Ast::Op::GT)(">=", Ast::Op::GTE) //
("<", Ast::Op::LT)("<=", Ast::Op::LTE) //
("=", Ast::Op::Assign);
return as<Ast::Op>( //
&identifier >> kw(sym) // if named operator, require keyword boundary
| sym,
"opsym");
}();
Note here that we conditionally apply the kw() modification on the operator symbol if the input token looks like alphanumeric. That, again, is to prevent andalucia or orlando from misparsing as the logical operators.
The condition &identifier is a bit sloppy, but it saves us from separating the interpunction operators from the named ones. Your profiler will tell you which is better.
auto nil = AST(Nil, kw("nil"));
auto arguments = AST(Arguments, &x3::lit(")") | expression % ",");
// this and super are just builtin identifiers
auto primary = AST(Expression,
bool_ | nil | number | string | non_reserved | "(" >> expression >> ")");
Note that I pruned "this" and "super" from the list as they are
just like other variables. If you opt to make them reserved, you will
need to special-case them here, e.g.
auto this_ = AST(Identifier, kw(x3::string("this")));
auto super_ = AST(Identifier, kw(x3::string("super")));
Smooth Operators
You already noticed the way using semantic actions. I separate out a few semantic action helpers:
auto assign = [](auto& ctx) {
_val(ctx) = _attr(ctx);
};
auto mk_call = [](auto& ctx) {
Ast::Expression expr = _val(ctx);
Ast::Arguments args = _attr(ctx);
_val(ctx) = Ast::FunctionCall{expr, args};
};
auto mk_member = [](auto& ctx) {
Ast::Expression obj = _val(ctx);
Ast::Identifier mem = _attr(ctx);
_val(ctx) = Ast::MemberAccess{obj, mem};
};
auto mk_unary = [](auto& ctx) {
auto& op = at_c<0>(_attr(ctx));
auto& rhs = at_c<1>(_attr(ctx));
_val(ctx) = Ast::Unary{op, rhs};
};
auto mk_binary = [](auto& ctx) {
auto& attr = _attr(ctx);
auto& op = at_c<0>(attr);
auto& rhs = at_c<1>(attr);
_val(ctx) = Ast::Binary{op, _val(ctx), rhs};
};
With these you can do the simples:
auto call_def = primary[assign] >> //
*(("(" >> arguments >> ")")[mk_call] //
| "." >> non_reserved[mk_member] //
);
auto unary_def = (expect_op("!", "-") >> unary)[mk_unary] | call[assign];
auto assignment_def = //
(call[assign] >> (expect_op("=") >> assignment)[mk_binary]) | //
logic_or[assign];
Then the bulk would become e.g.:
auto logic_or_def = logic_and[assign] >> *(&kw("or") >> opsym >> logic_and)[mk_binary];
To avoid the duplication let's make a rule factory:
auto binary_def = [](auto precedent, auto... ops) {
return precedent[assign] >> *(expect_op(ops...) >> precedent)[mk_binary];
};
The expect_op factory handles multiple acceptable operators, and applies proper token boundary checking again:
auto expect_op(auto... ops) {
return &x3::lexeme[
// keyword operator?
(&identifier >> kw((x3::as_parser(ops) | ...))) |
// interpunction operator
((x3::as_parser(ops) | ...) >> !x3::char_("!=><)"))] >>
opsym;
};
Now all the binaries (except the top level assignment, which has special associativity and lhs productions) become:
auto factor_def = binary_def(unary, "/", "*");
auto term_def = binary_def(factor, "-", "+");
auto comparison_def = binary_def(term, ">", ">=", "<", "<=");
auto equality_def = binary_def(comparison, "!=", "==");
auto logic_and_def = binary_def(equality, "and");
auto logic_or_def = binary_def(logic_and, "or");
Tieing it all together:
auto expression_def = assignment;
BOOST_SPIRIT_DEFINE(declaration, statement, expression);
BOOST_SPIRIT_DEFINE(call, unary, factor, term, comparison, equality, logic_and,
logic_or, assignment);
Testing
int main() {
#ifdef COLIRU
std::string input(std::istreambuf_iterator<char>(std::cin), {});
#else
std::string_view input = R"~(
class Cat < Animal {
Cat(name) {
print format("maybe implement member data some day: {}\n", name);
}
bark(volume) {
for (dummy = Nil; volume>0; volume = volume - 1)
print "bark!";
if (dummy or !(dummy == Nil) and universe_sane()) {
while(dummy) {{ print "(just kidding)"; }}
} else if (nesting() == "the shit") {
print("cool beans"); // extra parentheses are fine
return(True != False); // also on return statements
} else brackets = !"required";
return False;
}
bite() { return "pain takes no arguments"; }
}
var pooky = Cat("Pooky");
pooky.bark(10);
pooky = nil; // pooky got offed for being obnoxious :(
)~";
#endif
{
if (Ast::Program parsed;
parse(begin(input), end(input), Grammar::program, parsed))
std::cout << parsed << "\n";
else
std::cout << "Failed\n";
}
}
Live On Coliru Printing
class `Cat` < `Animal`{
[fun] `Cat`(`name`) {
print (`format`("maybe implement member data some day: {}\\n",`name`));
}
[fun] `bark`(`volume`) {
for((`dummy` = Nil); (`volume` > 0); (`volume` = (`volume` - 1)))
print "bark!";
if((`dummy` or ((! (`dummy` == Nil)) and (`universe_sane`())))) {
while(`dummy`)
{
{
print "(just kidding)";
}
}
}
else if(((`nesting`()) == "the shit")) {
print "cool beans";
return (True != False);
}
else (`brackets` = (! "required"))
return False;
}
[fun] `bite`() {
return "pain takes no arguments";
}
}
var `pooky` = (`Cat`("Pooky"));
((`pooky`.`bark`(10))
(`pooky` = Nil)
Locally, interactively:
Full Listing (anti-bitrot)
Sadly [SO] refuses it for length limits. I'll post it on Github. Link coming.
TL;DR
I think the at_c<N> accessor trick to dissect Fusion sequences in semantic action will help a lot.
Also, keep in mind that I don't think this rule structure is good for performant parsers. Just look at how something simple like x = y + (2); will invoke 43 rules (!!!) nested to 32 levels deep (!!!).
That's... not ideal. I've made a fully C++-compatible expression grammar (complete with interpreter) on SO before, and you can witness it here: https://github.com/sehe/qi-extended-parser-evaluator. It's using Spirit Qi, but in spirit it uses an almost X3 approach. I might make an X3 version of it just to compare for myself.
The key difference is that it generically implements operators with some metadata to describe it (token, precedence, associativity). This information is then used to combine expression AST nodes correctly. It even allows to get rid of redundant parentheses, both when building the Ast and when printing.
The interpreter logic (with dynamic type system, some reflection and execution tracing) may be a nice bonus inspiration: https://github.com/sehe/qi-extended-parser-evaluator/blob/master/eval.h#L291
Here is how I have solved the issue.
Instead of having a single binary ast node that stores a string of either "*" or "/", I split it up into separate ast node types for divide and multiply.
I then used the same machinery suggested by #sehe in the linked answer to synthesize the right nodes.
I'm still unsure how you can use semantic actions to synthesize attributes that span accross multiple '>>' operators. I'm guessing that the _val(ctx) in the semantic action refers to the whole ast::Expr across the currently defined rule so maybe you can set one member of a ast::Binary (eg the op string from the x3::string("*"), then in the next term after the '>>' you write _val(ctx) again (copy construct from previous?) and set the next member from the _attr(ctx)? I'll see if I can investigate if that works next. That would allow some more complex synthesizing of Attributes. Although I'm not sure if you could have different types being set accross the rule.
#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 Divide;
struct Multiply;
struct Unary;
struct Variable
{
std::string name;
// operator std::string() const {
// return 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<Multiply>, x3::forward_ast<Divide>, 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 Logical
// {
// Expr left_;
// std::string op_;
// Expr right_;
// };
struct Multiply
{
Expr left_;
Expr right_;
};
struct Divide
{
Expr left_;
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::Divide const &bin) const
{
_os << "divide { ";
bin.left_.apply_visitor(*this);
bin.right_.apply_visitor(*this);
_os << " }";
};
void operator()(hlsl::ast::Multiply const &bin) const
{
_os << "multiply { ";
bin.left_.apply_visitor(*this);
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::Multiply, left_, right_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Divide, left_, 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::Expr> const binary{"binary"};
x3::rule<struct multiply_, hlsl::ast::Expr> const multiply{"multiply"};
x3::rule<struct divide_, hlsl::ast::Expr> const divide{"divide"};
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 as_multiply = [](auto &ctx)
{ _val(ctx) = ast::Multiply{std::move(_val(ctx)), std::move(_attr(ctx))}; };
auto as_divide = [](auto &ctx)
{ _val(ctx) = ast::Divide{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("-") >> unarycallwrapper);
auto unarycallwrapper_def = call | unary;
auto binary_def = unarycallwrapper[as_expr] >> *((x3::lit('/') >> unarycallwrapper[as_divide]) | (x3::lit('*') >> unarycallwrapper[as_multiply]));
auto program_def = x3::skip(x3::space)[expression];
BOOST_SPIRIT_DEFINE(primary, assign, binary, multiply, divide, 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",
"this * that / there",
"this.inner * that * there.inner2",
}) //
{
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";
}
}
}
I also figured out how the semantic actions write to _val(ctx) across multiple sequence '>>' operators. You can write to them with the type that you need and it gets passed to the next one!
See binary2 rule and how it's def uses two semantic actions to write a Binary2 ast node and set different members each time.
#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 Divide;
struct Multiply;
struct Unary;
struct Binary2;
struct Variable
{
std::string name;
// operator std::string() const {
// return 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<Multiply>, x3::forward_ast<Binary2>, x3::forward_ast<Divide>, 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 Logical
// {
// Expr left_;
// std::string op_;
// Expr right_;
// };
struct Multiply
{
Expr left_;
Expr right_;
};
struct Binary2
{
Expr left_;
std::string op_;
Expr right_;
};
struct Divide
{
Expr left_;
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::Divide const &bin) const
{
_os << "divide { ";
bin.left_.apply_visitor(*this);
bin.right_.apply_visitor(*this);
_os << " }";
};
void operator()(hlsl::ast::Multiply const &bin) const
{
_os << "multiply { ";
bin.left_.apply_visitor(*this);
bin.right_.apply_visitor(*this);
_os << " }";
};
void operator()(hlsl::ast::Binary2 const &bin) const
{
_os << "binary2 { ";
bin.left_.apply_visitor(*this);
_os << 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::Multiply, left_, right_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Binary2, left_, op_, right_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Divide, left_, 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 binop_, std::string> const binop{"binop"};
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::Expr> const binary{"binary"};
x3::rule<struct binary2_, hlsl::ast::Expr> const binary2{"binary2"};
x3::rule<struct multiply_, hlsl::ast::Expr> const multiply{"multiply"};
x3::rule<struct divide_, hlsl::ast::Expr> const divide{"divide"};
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 as_multiply = [](auto &ctx)
{ _val(ctx) = ast::Multiply{std::move(_val(ctx)), std::move(_attr(ctx))}; };
auto as_divide = [](auto &ctx)
{ _val(ctx) = ast::Divide{std::move(_val(ctx)), std::move(_attr(ctx))}; };
auto as_binary2A = [](auto &ctx)
{ _val(ctx) = ast::Binary2{std::move(_val(ctx)), std::move(_attr(ctx)), ast::Expr{}}; };
auto as_binary2B = [](auto &ctx)
{ //_val(ctx) = std::move(_val(ctx));
boost::get<x3::forward_ast<ast::Binary2>>(_val(ctx)).get().right_ = 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) | binary2; // 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("-") >> unarycallwrapper);
auto unarycallwrapper_def = unary | call;
auto binop_def = x3::string("*") | x3::string("/");
auto binary_def = unarycallwrapper[as_expr] >> *((x3::lit('/') >> unarycallwrapper[as_divide]) | (x3::lit('*') >> unarycallwrapper[as_multiply]));
auto binary2_def = unarycallwrapper[as_expr] >> *(binop[as_binary2A] >> unarycallwrapper[as_binary2B]);
auto program_def = x3::skip(x3::space)[expression];
BOOST_SPIRIT_DEFINE(primary, assign, binop, binary, binary2, 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",
"this * that / there",
"this.inner * that * there.inner2",
}) //
{
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";
}
}
}
One more post...
I'm almost finished with the full stack of expression operators that hlsl uses (bitwise, logical, compound assignment etc).
I even figured out nested ternary operators which I thought would be really hard but didn't turn out too bad.
#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 CompoundAssign;
struct Divide;
struct Multiply;
struct Unary;
struct Binary;
struct Logical;
struct Bitwise;
struct Ternary;
struct Variable
{
std::string name;
// operator std::string() const {
// return 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<CompoundAssign>,
x3::forward_ast<Multiply>,
x3::forward_ast<Binary>,
x3::forward_ast<Logical>,
x3::forward_ast<Ternary>,
x3::forward_ast<Bitwise>,
x3::forward_ast<Divide>,
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 CompoundAssign
{
std::string name_;
std::string op_;
Expr value_;
};
struct Multiply
{
Expr left_;
Expr right_;
};
struct Binary
{
Expr left_;
std::string op_;
Expr right_;
};
struct Logical
{
Expr left_;
std::string op_;
Expr right_;
};
struct Bitwise
{
Expr left_;
std::string op_;
Expr right_;
};
struct Divide
{
Expr left_;
Expr right_;
};
struct Unary
{
std::string op_;
Expr expr_;
};
struct Ternary
{
Expr condition_;
Expr ifexpr_;
Expr elseexpr_;
};
} // 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::CompoundAssign const &assign) const
{
_os << "compoundAssign { ";
_os << "name_:" << quoted(assign.name_);
_os << "op_:" << quoted(assign.op_);
_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::Divide const &bin) const
{
_os << "divide { ";
bin.left_.apply_visitor(*this);
bin.right_.apply_visitor(*this);
_os << " }";
};
void operator()(hlsl::ast::Multiply const &bin) const
{
_os << "multiply { ";
bin.left_.apply_visitor(*this);
bin.right_.apply_visitor(*this);
_os << " }";
};
void operator()(hlsl::ast::Binary const &bin) const
{
_os << "binary { ";
bin.left_.apply_visitor(*this);
_os << bin.op_ << ", ";
bin.right_.apply_visitor(*this);
_os << " }";
};
void operator()(hlsl::ast::Logical const &bin) const
{
_os << "logical { ";
bin.left_.apply_visitor(*this);
_os << bin.op_ << ", ";
bin.right_.apply_visitor(*this);
_os << " }";
};
void operator()(hlsl::ast::Bitwise const &bin) const
{
_os << "bitwise { ";
bin.left_.apply_visitor(*this);
_os << 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::Ternary const &tern) const
{
_os << "ternary { ";
tern.condition_.apply_visitor(*this);
tern.ifexpr_.apply_visitor(*this);
tern.elseexpr_.apply_visitor(*this);
_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::CompoundAssign, name_, op_, value_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Multiply, left_, right_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Binary, left_, op_, right_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Logical, left_, op_, right_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Bitwise, left_, op_, right_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Divide, left_, right_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Unary, op_, expr_)
BOOST_FUSION_ADAPT_STRUCT(hlsl::ast::Ternary, condition_, ifexpr_, elseexpr_)
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 factor_, std::string> const factor{"factor"};
x3::rule<struct term_, std::string> const term{"term"};
x3::rule<struct compare_op_, std::string> const compare_op{"compare_op"};
x3::rule<struct equality_op_, std::string> const equality_op{"equality_op"};
x3::rule<struct compoundassign_op_, std::string> const compoundassign_op{"compoundassign_op"};
x3::rule<struct bitwise_shift_op_, std::string> const bitwise_shift_op{"bitwise_shift_op"};
x3::rule<struct variable_, ast::Variable> const variable{"variable"};
x3::rule<struct arguments_, std::vector<ast::Expr>> const arguments{"arguments_"};
x3::rule<struct bitwise_or_, hlsl::ast::Expr> const bitwise_or{"bitwise_or"};
x3::rule<struct bitwise_xor_, hlsl::ast::Expr> const bitwise_xor{"bitwise_xor"};
x3::rule<struct bitwise_and_, hlsl::ast::Expr> const bitwise_and{"bitwise_and"};
x3::rule<struct bitwise_shift_, hlsl::ast::Expr> const bitwise_shift{"bitwise_shift"};
x3::rule<struct addition_, hlsl::ast::Expr> const addition{"addition"};
x3::rule<struct comparison_, hlsl::ast::Expr> const comparison{"comparison"};
x3::rule<struct equality_, hlsl::ast::Expr> const equality{"equality"};
x3::rule<struct logical_or_, hlsl::ast::Expr> const logical_or{"logical_or"};
x3::rule<struct logical_and_, hlsl::ast::Expr> const logical_and{"logical_and"};
x3::rule<struct multiply_, hlsl::ast::Expr> const multiply{"multiply"};
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 compoundassign_, ast::CompoundAssign> const compoundassign{"compoundassign"};
x3::rule<struct ternary_, ast::Expr> const ternary{"ternary"};
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 get_string_from_variable_cast = [](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 as_binary_op = [](auto &ctx)
{ _val(ctx) = ast::Binary{std::move(_val(ctx)), std::move(_attr(ctx)), ast::Expr{}}; };
auto as_binary_wrap = [](auto &ctx)
{ boost::get<x3::forward_ast<ast::Binary>>(_val(ctx)).get().right_ = std::move(_attr(ctx)); };
auto as_logical_op = [](auto &ctx)
{ _val(ctx) = ast::Logical{std::move(_val(ctx)), std::move(_attr(ctx)), ast::Expr{}}; };
auto as_logical_wrap = [](auto &ctx)
{ boost::get<x3::forward_ast<ast::Logical>>(_val(ctx)).get().right_ = std::move(_attr(ctx)); };
auto as_bitwise_op = [](auto &ctx)
{ _val(ctx) = ast::Bitwise{std::move(_val(ctx)), std::move(_attr(ctx)), ast::Expr{}}; };
auto as_bitwise_wrap = [](auto &ctx)
{ boost::get<x3::forward_ast<ast::Bitwise>>(_val(ctx)).get().right_ = std::move(_attr(ctx)); };
auto as_compound_op = [](auto &ctx)
{ _val(ctx).op_ = std::move(_attr(ctx)); };
auto as_ternary_ifexpr = [](auto &ctx)
{ _val(ctx) = ast::Ternary{std::move(_val(ctx)), std::move(_attr(ctx)), ast::Expr{}}; };
auto as_ternary_elseexpr = [](auto &ctx)
{ boost::get<x3::forward_ast<ast::Ternary>>(_val(ctx)).get().elseexpr_ = std::move(_attr(ctx)); };
auto as_compound_wrap = [](auto &ctx)
{ boost::get<x3::forward_ast<ast::CompoundAssign>>(_val(ctx)).get().value_ = 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 expression_def = assignment;
auto variable_def = identifier;
auto identifier_def = x3::lexeme[x3::alpha >> *x3::alnum];
auto set_def = (get >> '=' >> assignment)[fold_in_get_to_set];
auto arguments_def = *(expression % ',');
auto factor_def = x3::string("*") | x3::string("/");
auto term_def = x3::string("+") | x3::string("-");
auto compare_op_def = x3::string("<=") | x3::string(">=") | x3::string("<") | x3::string(">");
auto equality_op_def = x3::string("!=") | x3::string("==");
auto compoundassign_op_def = x3::string("*=") | x3::string("/=") | x3::string("%=") | x3::string("+=")
| x3::string("-=") | x3::string("<<=") | x3::string(">>=")
| x3::string("&=") | x3::string("^=") | x3::string("|=");
auto bitwise_shift_op_def = x3::string(">>") | x3::string("<<");
//auto binary_def = unarycallwrapper[as_expr] >> *((x3::lit('/') >> unarycallwrapper[as_divide]) | (x3::lit('*') >> unarycallwrapper[as_multiply]));
auto assign_def = variable[get_string_from_variable] >> '=' >> assignment[fix_assignExpr];
auto compoundassign_def = variable[get_string_from_variable] >> compoundassign_op[as_compound_op] >> assignment[fix_assignExpr];
auto assignment_def = (assign | compoundassign | set) | ternary;
auto ternary_def = logical_or[as_expr] >> *('?' >> expression[as_ternary_ifexpr] >> ':' >> ternary[as_ternary_elseexpr]);
auto logical_or_def = logical_and[as_expr] >> *(x3::string("||")[as_logical_op] >> logical_and[as_logical_wrap]);
auto logical_and_def = bitwise_or[as_expr] >> *(x3::string("&&")[as_logical_op] >> bitwise_or[as_logical_wrap]);
auto bitwise_or_def = bitwise_xor[as_expr] >> *((x3::string("|") >> !(x3::lit('|') | x3::lit('=')))[as_bitwise_op] >> bitwise_xor[as_bitwise_wrap]);
auto bitwise_xor_def = bitwise_and[as_expr] >> *((x3::string("^") > !(x3::lit('^') | x3::lit('=')))[as_bitwise_op] >> bitwise_and[as_bitwise_wrap]);
auto bitwise_and_def = equality[as_expr] >> *((x3::string("&") >> !(x3::lit('&') | x3::lit('=')))[as_bitwise_op] >> equality[as_bitwise_wrap]);
auto equality_def = comparison[as_expr] >> *(equality_op[as_binary_op] >> comparison[as_binary_wrap]);
auto comparison_def = bitwise_shift[as_expr] >> *(compare_op[as_binary_op] >> bitwise_shift[as_binary_wrap]);
auto bitwise_shift_def = addition[as_expr] >> *(bitwise_shift_op[as_binary_op] >> addition[as_binary_wrap]);
auto addition_def = multiply[as_expr] >> *(term[as_binary_op] >> multiply[as_binary_wrap]);
auto multiply_def = unarycallwrapper[as_expr] >> *(factor[as_binary_op] >> unarycallwrapper[as_binary_wrap]);
auto unarycallwrapper_def = unary | call;
auto unary_def = (x3::string("-") >> unarycallwrapper);
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 primary_def = variable;
auto program_def = x3::skip(x3::space)[expression];
BOOST_SPIRIT_DEFINE(primary, assign,
compoundassign, compoundassign_op, bitwise_or, bitwise_xor,
ternary,
bitwise_and, bitwise_shift, bitwise_shift_op,
logical_and, logical_or, equality_op,
equality, factor, compare_op, comparison,
term, addition, multiply, 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",
"this * that / there",
"this.inner * that * there.inner2",
"first + second",
"first + second * third",
"first < second",
"first <= second * third",
"first - second > third",
"first != second",
"first == second * third",
"first || second",
"first || second && third"
"first |= second",
"first |= second.third",
"first & second",
"first & second && third",
"first &= second && third",
"first << second && third",
"first ^ second",
"first ^ second ^^ third", //fails on purpose because this operator doesn't exist!
"zero |= first | second || third",
"first ? second : third",
"first > second ? third : fourth",
"first > second ? third : fourth > fifth ? sixth : seventh"
}) //
{
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";
}
}
}

Spirit.X3: passing local data to a parser

The examples in the Boost.Spirit documentation seem to fall in two cases:
1/ Define a parser in a function: semantic actions can access local variables and data as they are local lambdas. Like push_back here: https://www.boost.org/doc/libs/master/libs/spirit/doc/x3/html/spirit_x3/tutorials/number_list___stuffing_numbers_into_a_std__vector.html
2/ Define a parser in a namespace, like here: https://www.boost.org/doc/libs/1_69_0/libs/spirit/doc/x3/html/spirit_x3/tutorials/minimal.html
which seems to be necessary to be able to invoke BOOST_SPIRIT_DEFINE.
My question is: how to combine both (properly, without globals) ? My dream API would be to pass some argument to phrase_parse and then do some x3::_arg(ctx) but I couldn't find anything like this.
Here is for instance my parser: for now the actions are writing to std::cerr. What if I wanted to write to a custom std::ostream& instead, that would be passed to the parse function?
using namespace boost::spirit;
using namespace boost::spirit::x3;
rule<struct id_action> action = "action";
rule<struct id_array> array = "array";
rule<struct id_empty_array> empty_array = "empty_array";
rule<struct id_atom> atom = "atom";
rule<struct id_sequence> sequence = "sequence";
rule<struct id_root> root = "root";
auto access_index_array = [] (const auto& ctx) { std::cerr << "access_array: " << x3::_attr(ctx) << "\n" ;};
auto access_empty_array = [] (const auto& ctx) { std::cerr << "access_empty_array\n" ;};
auto access_named_member = [] (const auto& ctx) { std::cerr << "access_named_member: " << x3::_attr(ctx) << "\n" ;};
auto start_action = [] (const auto& ctx) { std::cerr << "start action\n" ;};
auto finish_action = [] (const auto& ctx) { std::cerr << "finish action\n" ;};
auto create_array = [] (const auto& ctx) { std::cerr << "create_array\n" ;};
const auto action_def = +(lit('.')[start_action]
>> -((+alnum)[access_named_member])
>> *(('[' >> x3::int_ >> ']')[access_index_array] | lit("[]")[access_empty_array]));
const auto sequence_def = (action[finish_action] % '|');
const auto array_def = ('[' >> sequence >> ']')[create_array];
const auto root_def = array | action;
BOOST_SPIRIT_DEFINE(action)
BOOST_SPIRIT_DEFINE(array)
BOOST_SPIRIT_DEFINE(sequence)
BOOST_SPIRIT_DEFINE(root)
bool parse(std::string_view str)
{
using ascii::space;
auto first = str.begin();
auto last = str.end();
bool r = phrase_parse(
first, last,
parser::array_def | parser::sequence_def,
ascii::space
);
if (first != last)
return false;
return r;
}
About the approaches:
1/ Yes, this is viable for small, contained parsers. Typically only used in a single TU, and exposed via non-generic interface.
2/ This is the approach for (much) larger grammars, that you might wish to spread across TUs, and/or are instantiated across several TU's generically.
Note that you do NOT need BOOST_SPIRIT_DEFINE unless you
have recursive rules
want to split declaration from definition. [This becomes pretty complicated, and I recommend against using that for X3.]
The Question
My question is: how to combine both (properly, without globals) ?
You can't combine something with namespace level declarations, if one of the requiremenents is "without globals".
My dream API would be to pass some argument to phrase_parse and then do some x3::_arg(ctx) but I couldn't find anything like this.
I don't know what you think x3::_arg(ctx) would do, in that particular dream :)
Here is for instance my parser: for now the actions are writing to std::cerr. What if I wanted to write to a custom std::ostream& instead, that would be passed to the parse function?
Now that's a concrete question. I'd say: use the context.
You could make it so that you can use x3::get<ostream>(ctx) returns the stream:
struct ostream{};
auto access_index_array = [] (const auto& ctx) { x3::get<ostream>(ctx) << "access_array: " << x3::_attr(ctx) << "\n" ;};
auto access_empty_array = [] (const auto& ctx) { x3::get<ostream>(ctx) << "access_empty_array\n" ;};
auto access_named_member = [] (const auto& ctx) { x3::get<ostream>(ctx) << "access_named_member: " << x3::_attr(ctx) << "\n" ;};
auto start_action = [] (const auto& ctx) { x3::get<ostream>(ctx) << "start action\n" ;};
auto finish_action = [] (const auto& ctx) { x3::get<ostream>(ctx) << "finish action\n" ;};
auto create_array = [] (const auto& ctx) { x3::get<ostream>(ctx) << "create_array\n";};
Now you need to put the tagged param in the context during parsing:
bool r = phrase_parse(
f, l,
x3::with<parser::ostream>(std::cerr)[parser::array_def | parser::sequence_def],
x3::space);
Live Demo: http://coliru.stacked-crooked.com/a/a26c8eb0af6370b9
Prints
start action
access_named_member: a
finish action
start action
access_named_member: b
start action
start action
access_array: 2
start action
access_named_member: foo
start action
access_empty_array
finish action
start action
access_named_member: c
finish action
create_array
true
Intermixed with the standard X3 debug output:
<sequence>
<try>.a|.b..[2].foo.[]|.c</try>
<action>
<try>.a|.b..[2].foo.[]|.c</try>
<success>|.b..[2].foo.[]|.c]</success>
</action>
<action>
<try>.b..[2].foo.[]|.c]</try>
<success>|.c]</success>
</action>
<action>
<try>.c]</try>
<success>]</success>
</action>
<success>]</success>
</sequence>
But Wait #1 - Event Handlers
It looks like you're parsing something similar to JSON Pointer or jq syntax. In the case that you wanted to provide a callback-interface (SAX-events), why not bind the callback interface instead of the actions:
struct handlers {
using N = x3::unused_type;
virtual void index(int) {}
virtual void index(N) {}
virtual void property(std::string) {}
virtual void start(N) {}
virtual void finish(N) {}
virtual void create_array(N) {}
};
#define EVENT(e) ([](auto& ctx) { x3::get<handlers>(ctx).e(x3::_attr(ctx)); })
const auto action_def =
+(x3::lit('.')[EVENT(start)] >> -((+x3::alnum)[EVENT(property)]) >>
*(('[' >> x3::int_ >> ']')[EVENT(index)] | x3::lit("[]")[EVENT(index)]));
const auto sequence_def = action[EVENT(finish)] % '|';
const auto array_def = ('[' >> sequence >> ']')[EVENT(create_array)];
const auto root_def = array | action;
Now you can implement all handlers neatly in one interface:
struct default_handlers : parser::handlers {
std::ostream& os;
default_handlers(std::ostream& os) : os(os) {}
void index(int i) override { os << "access_array: " << i << "\n"; };
void index(N) override { os << "access_empty_array\n" ; };
void property(std::string n) override { os << "access_named_member: " << n << "\n" ; };
void start(N) override { os << "start action\n" ; };
void finish(N) override { os << "finish action\n" ; };
void create_array(N) override { os << "create_array\n"; };
};
auto f = str.begin(), l = str.end();
bool r = phrase_parse(f, l,
x3::with<parser::handlers>(default_handlers{std::cout}) //
[parser::array_def | parser::sequence_def],
x3::space);
See it Live On Coliru once again:
start action
access_named_member: a
finish action
start action
access_named_member: b
start action
start action
access_array: 2
start action
access_named_member: foo
start action
access_empty_array
finish action
start action
access_named_member: c
finish action
create_array
true
But Wait #2 - No Actions
The natural way to expose attributes would be to build an AST. See also Boost Spirit: "Semantic actions are evil"?
Without further ado:
namespace AST {
using Id = std::string;
using Index = int;
struct Member {
std::optional<Id> name;
};
struct Indexer {
std::optional<int> index;
};
struct Action {
Member member;
std::vector<Indexer> indexers;
};
using Actions = std::vector<Action>;
using Sequence = std::vector<Actions>;
struct ArrayCtor {
Sequence actions;
};
using Root = boost::variant<ArrayCtor, Actions>;
}
Of course, I'm making some assumptions. The rules can be much simplified:
namespace parser {
template <typename> struct Tag {};
#define AS(T, p) (x3::rule<Tag<AST::T>, AST::T>{#T} = p)
auto id = AS(Id, +x3::alnum);
auto member = AS(Member, x3::lit('.') >> -id);
auto indexer = AS(Indexer,'[' >> -x3::int_ >> ']');
auto action = AS(Action, member >> *indexer);
auto actions = AS(Actions, +action);
auto sequence = AS(Sequence, actions % '|');
auto array = AS(ArrayCtor, '[' >> -sequence >> ']'); // covers empty array
auto root = AS(Root, array | actions);
} // namespace parser
And the parsing function returns the AST:
AST::Root parse(std::string_view str) {
auto f = str.begin(), l = str.end();
AST::Root parsed;
phrase_parse(f, l, x3::expect[parser::root >> x3::eoi], x3::space, parsed);
return parsed;
}
(Note that it now throws x3::expection_failure if the input is invalid or not completely parsed)
int main() {
std::cout << parse("[.a|.b..[2].foo.[]|.c]");
}
Now prints:
[.a|.b./*none*/./*none*/[2].foo./*none*/[/*none*/]|.c]
See it Live On Coliru
//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <ostream>
#include <optional>
namespace x3 = boost::spirit::x3;
namespace AST {
using Id = std::string;
using Index = int;
struct Member {
std::optional<Id> name;
};
struct Indexer {
std::optional<int> index;
};
struct Action {
Member member;
std::vector<Indexer> indexers;
};
using Actions = std::vector<Action>;
using Sequence = std::vector<Actions>;
struct ArrayCtor {
Sequence actions;
};
using Root = boost::variant<ArrayCtor, Actions>;
}
BOOST_FUSION_ADAPT_STRUCT(AST::Member, name)
BOOST_FUSION_ADAPT_STRUCT(AST::Indexer, index)
BOOST_FUSION_ADAPT_STRUCT(AST::Action, member, indexers)
BOOST_FUSION_ADAPT_STRUCT(AST::ArrayCtor, actions)
namespace parser {
template <typename> struct Tag {};
#define AS(T, p) (x3::rule<Tag<AST::T>, AST::T>{#T} = p)
auto id = AS(Id, +x3::alnum);
auto member = AS(Member, x3::lit('.') >> -id);
auto indexer = AS(Indexer,'[' >> -x3::int_ >> ']');
auto action = AS(Action, member >> *indexer);
auto actions = AS(Actions, +action);
auto sequence = AS(Sequence, actions % '|');
auto array = AS(ArrayCtor, '[' >> -sequence >> ']'); // covers empty array
auto root = AS(Root, array | actions);
} // namespace parser
AST::Root parse(std::string_view str) {
auto f = str.begin(), l = str.end();
AST::Root parsed;
phrase_parse(f, l, x3::expect[parser::root >> x3::eoi], x3::space, parsed);
return parsed;
}
// for debug output
#include <iostream>
#include <iomanip>
namespace AST {
static std::ostream& operator<<(std::ostream& os, Member const& m) {
return os << "." << m.name.value_or("/*none*/");
}
static std::ostream& operator<<(std::ostream& os, Indexer const& i) {
if (i.index)
return os << "[" << *i.index << "]";
else
return os << "[/*none*/]";
}
static std::ostream& operator<<(std::ostream& os, Action const& a) {
os << a.member;
for (auto& i : a.indexers)
os << i;
return os;
}
static std::ostream& operator<<(std::ostream& os, Actions const& aa) {
for (auto& a : aa)
os << a;
return os;
}
static std::ostream& operator<<(std::ostream& os, Sequence const& s) {
bool first = true;
for (auto& a : s)
os << (std::exchange(first, false) ? "" : "|") << a;
return os;
}
static std::ostream& operator<<(std::ostream& os, ArrayCtor const& ac) {
return os << "[" << ac.actions << "]";
}
}
int main() {
std::cout << parse("[.a|.b..[2].foo.[]|.c]");
}

Alternative attribute synthesis and AST design

In the grammar below, when I add the alternative (| property) to the start rule, I get this error
'boost::spirit::x3::traits::detail::move_to': none of the 3 overloads
could convert all the argument types
e:\data\boost\boost_1_65_1\boost\spirit\home\x3\support\traits\move_to.hpp 180
I suspect that the problem is that the property attribute is a struct, and property_list is a vector (shouldn't x3 create a vector of one entry?). What is the recommended way to design the AST structures to support alternatives? A Boost variant?
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <map>
#pragma warning(push)
#pragma warning(disable : 4348)
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/variant.hpp>
#include <boost/fusion/adapted/struct.hpp>
#pragma warning(pop)
namespace x3 = boost::spirit::x3;
namespace scl_ast
{
struct KEYWORD : std::string
{
using std::string::string;
using std::string::operator=;
};
struct NIL
{
};
using VALUE = boost::variant <NIL, std::string, int, double, KEYWORD>;
struct PROPERTY
{
KEYWORD name;
VALUE value;
};
static inline std::ostream& operator<< (std::ostream& os, VALUE const& v)
{
struct
{
std::ostream& _os;
void operator () (std::string const& s) const { _os << std::quoted (s); }
void operator () (int i) const { _os << i; }
void operator () (double d) const { _os << d; }
void operator () (KEYWORD const& k) const { _os << k; }
void operator () (NIL) const { }
} vis { os };
boost::apply_visitor (vis, v);
return os;
}
static inline std::ostream& operator<< (std::ostream& os, PROPERTY const& prop)
{
os << prop.name;
if (prop.value.which ())
{
os << "=" << prop.value;
}
return os;
}
static inline std::ostream& operator<< (std::ostream& os, std::vector <PROPERTY> const& props)
{
for (auto const& prop : props)
{
os << prop << " ";
}
return os;
}
}; // End namespace scl_ast
BOOST_FUSION_ADAPT_STRUCT (scl_ast::PROPERTY, name, value)
//
// Keyword-value grammar for simple command language
//
namespace scl
{
using namespace x3;
auto const keyword = rule <struct _keyword, std::string> { "keyword" }
= lexeme [+char_ ("a-zA-Z0-9$_")];
auto const quoted_string
= lexeme ['"' >> *('\\' > char_ | ~char_ ('"')) >> '"'];
auto const value
= quoted_string
| x3::real_parser<double, x3::strict_real_policies<double>>{}
| x3::int_
| keyword;
auto const property = rule <struct _property, scl_ast::PROPERTY> { "property" }
= keyword >> -(("=" >> value));
auto const property_list = rule <struct _property_list, std::vector <scl_ast::PROPERTY>> { "property_list" }
= lit ('(') >> property % ',' >> lit (')');
auto const start = skip (blank) [property_list | property];
}; // End namespace scl
int
main ()
{
std::vector <std::string> input =
{
"(abc=1.,def=.5,ghi=2.0)",
"(ghi = 1, jkl = 3)",
"(abc,def=1,ghi=2.4,jkl=\"mno 123\", pqr = stu)",
"(abc = test, def, ghi=2)",
"abc=1",
"def = 2.7",
"ghi"
};
for (auto const& str : input)
{
std::vector <scl_ast::PROPERTY> result;
auto b = str.begin (), e = str.end ();
bool ok = x3::parse (b, e, scl::start, result);
std::cout << (ok ? "OK" : "FAIL") << '\t' << std::quoted (str) << std::endl;
if (ok)
{
std::cout << " -- Parsed: " << result << std::endl;
if (b != e)
{
std::cout << " -- Unparsed: " << std::quoted (std::string (b, e)) << std::endl;
}
}
std::cout << std::endl;
} // End for
return 0;
} // End main
I suspect that the problem is that the property attribute is a struct, and property_list is a vector (shouldn't x3 create a vector of one entry?)
Yes, yes, and yes, depending.
I see roughly 3 approaches to tackle this. Let's start with the simples:
Force Container-like synthesis: Live On Coliru
= skip(blank)[property_list | repeat(1)[property] ];
Coercing the attribute type. Turns out I was wrong here: It used to work for Qi, but apparently that has been dropped. Here it is, not-working and all:
auto coerce = [](auto p) { return rule<struct _, std::vector<scl_ast::PROPERTY> > {} = p; };
auto const start
= skip(blank)[property_list | coerce(property)];
Third is actually moot because the same problem. So I guess I owe you a contrived workaround, using semantic actions: Live On Coliru
auto push_back = [](auto& ctx) {
_val(ctx).push_back(_attr(ctx));
};
auto const start
= rule<struct _start, std::vector<scl_ast::PROPERTY>, true>{ "start" }
= skip(blank)[property_list | omit[property[push_back]]];

boost.spirit x3 move_to and list ast member

the BNF I implement has a funny rule where, depending on operator, the terms can be chained or event not at this production rule. Hence I use the same AST data structure since only the enumeration changes:
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <iostream>
#include <string>
#include <list>
namespace ast
{
struct identifer {
int name;
};
struct expression {
struct chunk {
char operator_;
ast::identifer identifer;
};
ast::identifer identifer;
std::list<chunk> chunk_list;
};
}
BOOST_FUSION_ADAPT_STRUCT(ast::identifer,
name
)
BOOST_FUSION_ADAPT_STRUCT(ast::expression::chunk,
operator_, identifer
)
BOOST_FUSION_ADAPT_STRUCT(ast::expression,
identifer, chunk_list
)
namespace boost { namespace spirit { namespace x3 { namespace traits {
void move_to(ast::expression::chunk&& chunk, std::list<ast::expression::chunk>& chunk_list,
mpl::identity<container_attribute>)
{
chunk_list.emplace(chunk_list.end(), std::move(chunk));
}
} } } }
namespace parser
{
namespace x3 = boost::spirit::x3;
auto const identifier = x3::rule<struct _, int> { "identifier" } =
x3::int_;
auto const operator_1 = x3::rule<struct _, char> { "operator" } =
x3::char_("ABC");
auto const operator_2 = x3::rule<struct _, char> { "operator" } =
x3::char_("XYZ");
auto const expression_chunk_1 = x3::rule<struct _, ast::expression::chunk> { "expression" } =
operator_1 > identifier
;
auto const expression_chunk_2 = x3::rule<struct _, ast::expression::chunk> { "expression" } =
operator_2 > identifier
;
auto const expression = x3::rule<struct _, ast::expression> { "expression" } =
identifier >> *expression_chunk_1 // foo { and foo }
// rule below fails to compile
| identifier >> expression_chunk_2 // foo [ nand foo ]
;
}
struct visitor {
visitor(std::ostream& os) : os{ os } { }
void operator()(ast::expression const& node) {
os << "(";
(*this)(node.identifer);
for(auto const& chunk : node.chunk_list) {
os << "(" << chunk.operator_ << " ";
(*this)(chunk.identifer);
os << ")";
}
os << ")\n";
}
void operator()(ast::identifer const& node) {
os << "(" << node.name << ")";
}
std::ostream& os;
};
int main()
{
namespace x3 = boost::spirit::x3;
for(std::string const str: {
"1 X 2",
"3 A 4 A 5"
}) {
auto iter = str.begin(), end = str.end();
ast::expression attr;
bool r = x3::phrase_parse(iter, end, parser::expression, x3::space, attr);
std::cout << "parse '" << str << "': ";
if (r && iter == end) {
std::cout << "succeeded:\n";
visitor(std::cout)(attr);
} else {
std::cout << "*** failed ***\n";
}
}
return 0;
}
This was the idea - the operator X,Y,Z adds only one chunk to list. Following the compiler errors, I have to specialize x3::traits::move_to, but I don't found any solution to get this to compile. What is the wayy to do? is the list::emplace() and std::move() safe here?
I'd do without the trait. Instead, make the grammar result in a vector<T> artificially using repeat:
auto const expression = x3::rule<struct _, ast::expression> { "expression" } =
identifier >> *expression_chunk_1 // foo { and foo }
| identifier >> x3::repeat(1) [ expression_chunk_2 ] // foo [ nand foo ]
;

boost qi attribute is coming up as qi::unused_type

Cannot figure out why this rule unary_msg doesnt work, it says the attribute type is qi::unused_type but this makes no sense to me. Why does boost torment me like this?
template<class It, class Skip= boost::spirit::ascii::space_type>
struct g3: qi::grammar<It, ast::expr(), Skip>
{
template<typename...Args>
using R = qi::rule<It, Args...>;
R<ast::expr(), Skip> start, expr_, term_, unary_term;
R<ast::intlit()> int_;
R<std::string()> selector_;
R<boost::fusion::vector<ast::expr, std::vector<std::string>>, Skip> unary_msg;
g3(): g3::base_type(start)
{
namespace ph = boost::phoenix;
using namespace boost::spirit::qi;
int_ = qi::int_;
selector_ = lexeme[+qi::alnum];
term_ = int_;
unary_msg = term_ >> *selector_;
unary_term = unary_msg[ qi::_val = ph::bind(&collect_unary, qi::_1) ];
expr_ = unary_term;
start = expr_;
}
};
full code: http://coliru.stacked-crooked.com/a/e9afef4585ce76c3
Like cv_and_he mentions, add the parens.
Working example with many cleanup suggestions:
Live On Coliru
Notes
don't use using namespace at toplevel
don't use conflicting namespaces (using std and boost are very likely to lead to surprises or conflicts)
don't use internal attribute types like fusion::vector
use modern style BOOST_FUSION_ADAPT_STRUCT
some minor style issues
For example the following function
ast::expr collect_unary (const boost::fusion::vector<ast::expr, std::vector<std::string>>& parts)
//ast::expr collect_unary (const ast::expr& a, const std::vector<std::string>& msgs)
{
ast::expr res = boost::fusion::at_c<0>(parts);//a;
const auto& msgs = boost::fusion::at_c<1>(parts);
for(const auto& m: msgs)
{
ast::message msg;
msg.name = m;
msg.args.push_back(res);
res = msg;
}
return res;
}
was changed into:
ast::expr collect_unary(ast::expr accum, const std::vector<std::string>& msgs) {
for (const auto &m : msgs)
accum = ast::message { m, { accum } };
return accum;
}
Full Listing And Output
Live On Coliru
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
namespace ast {
struct intlit {
int value;
intlit(int i = 0) : value(i) { }
intlit(intlit const&other) = default;
};
struct nil {};
struct message;
using expr = boost::make_recursive_variant<nil, intlit, message>::type;
struct message {
std::string name;
std::vector<ast::expr> args;
};
}
#include <boost/fusion/include/adapt_struct.hpp>
BOOST_FUSION_ADAPT_STRUCT(ast::intlit, value)
BOOST_FUSION_ADAPT_STRUCT(ast::message, name, args)
struct ast_print {
void operator()(ast::nil &) const { std::cout << "nil"; }
void operator()(ast::intlit &i) const { std::cout << i.value; }
void operator()(ast::message &m) const {
std::cout << "(" << m.name;
for (auto &it : m.args) {
std::cout << " ";
boost::apply_visitor(ast_print(), it);
}
std::cout << ")" << std::endl;
}
};
ast::expr collect_unary(ast::expr accum, const std::vector<std::string>& msgs)
{
for (const auto &m : msgs)
accum = ast::message { m, { accum } };
return accum;
}
template <class It, class Skip = boost::spirit::ascii::space_type> struct g3 : qi::grammar<It, ast::expr(), Skip> {
g3() : g3::base_type(start) {
using namespace boost::spirit::qi;
namespace ph = boost::phoenix;
int_ = qi::int_;
selector_ = +qi::alnum;
term_ = int_;
unary_msg = (term_ >> *selector_) [ _val = ph::bind(collect_unary, _1, _2) ];
unary_term = unary_msg;
expr_ = unary_term;
start = expr_;
}
private:
template <typename Attr, typename... Args> using R = qi::rule<It, Attr(), Args...>;
R<ast::expr, Skip> start, expr_, term_, unary_term, unary_msg;
R<ast::intlit> int_;
R<std::string> selector_;
};
template <class Parser, typename Result> bool test(const std::string &input, const Parser &parser, Result &result) {
auto first = input.begin(), last = input.end();
return qi::phrase_parse(first, last, parser, boost::spirit::ascii::space, result);
}
int main() {
std::string const input = "42 x y";
g3<std::string::const_iterator> p;
ast::expr res;
if (test(input, p, res)) {
std::cout << "parse ok " << std::endl;
boost::apply_visitor(ast_print(), res);
}
}
Prints
parse ok
(y (x 42)
)