-
I've tried to read through the source to get a better understanding of some of this stuff, but my grasp of c++ templates and constexpr is pretty weak. Hell, my grasp of c++ itself isn't great since I've spent more time coding in c# and other languages. I've got some simple production rules that work fine: struct cool_identifier : lexy::token_production {
static constexpr auto rule = dsl::identifier(dsl::ascii::alpha_digit_underscore);
static constexpr auto value = lexy::as_string<std::string>;
};
struct cool_boolean : lexy::token_production {
struct true_ : lexy::transparent_production
{
static constexpr auto rule = LEXY_LIT("true");
static constexpr auto value = lexy::constant(true);
};
struct false_ : lexy::transparent_production
{
static constexpr auto rule = LEXY_LIT("false");
static constexpr auto value = lexy::constant(false);
};
static constexpr auto rule = dsl::p<true_> | dsl::p<false_>;
static constexpr auto value = lexy::forward<bool>;
};
struct cool_integer : lexy::token_production {
static constexpr auto rule = [] {
auto digits = dsl::digits<>.sep(dsl::digit_sep_tick).no_leading_zero();
return dsl::integer<int>(digits);
}();
static constexpr auto value = lexy::forward<int>;
};
struct cool_string : lexy::token_production {
static constexpr auto rule = []{
auto code_point = (-dsl::unicode::control);
return dsl::quoted.limit(dsl::ascii::newline)(code_point);
}();
static constexpr auto value = lexy::as_string<std::string>;
}; However when I begin building more complicated objects my understanding begins to break down. A simple example would be parsing a function call's list of parameters. I've created a rule for a function call which, after rewriting some code several times, looks like this: struct function_call_expression : expression {
std::string identifier;
std::vector<literal_expression> temp; // for testing purposes. literal_expression was not a subclass of expression when I wrote this line
std::vector<expression_pointer> value;
};
... <snip>
struct cool_function_call {
static constexpr auto whitespace
= dsl::ascii::space
| LEXY_LIT("(*") >> dsl::until(LEXY_LIT("*)"))
| LEXY_LIT("--") >> (dsl::until(dsl::newline) | dsl::eof);
struct parameter_pack {
static constexpr auto rule = []{
auto item = dsl::p<cool_identifier> | dsl::p<cool_string> | dsl::p<cool_boolean>;
return dsl::parenthesized.list(item, dsl::trailing_sep(dsl::comma));
}();
static constexpr auto value = lexy::as_list<std::vector<ast::literal_expression>>;
};
static constexpr auto rule = []{
auto name_field = (LEXY_MEM(identifier) = dsl::p<cool_identifier>);
auto expression = (LEXY_MEM(temp) = dsl::p<parameter_pack>);
return name_field + expression;
}();
static constexpr auto value = lexy::as_aggregate<ast::function_call_expression>;
}; Note: whitespace is here only because I've been using this as my "top level" parser for testing at the moment. And it's not correct, since it fails to parse when Initially I had trouble even implementing this rule, since I began like the calculator example, defining a base struct boolean_constant_expression : expression {
explicit boolean_constant_expression(bool value)
: value(LEXY_MOV(value))
{}
};
struct function_call_expression : expression {
std::string identifier;
std::vector<expression_pointer> value;
};
... etc. <snip>
struct expression : lexy::expression_production {
struct expected_operand
{
static constexpr auto name = "expected operand";
};
static constexpr auto atom = []{
auto literal = dsl::p<cool_boolean> | dsl::p<cool_string> | dsl::p<cool_integer>;
auto identifier = dsl::p<cool_identifier>;
auto paren_expr = dsl::parenthesized(dsl::p<nested_expression>);
auto var_or_call = identifier >> dsl::if_(paren_expr); // I don't fully get how this line works either
return paren_expr | literal | var_or_call | dsl::error<expected_operand>;
}();
// All binary operations are left-associative, with the exception of assignment, which is right-associative,
// and the three comparison operations, which do not associate.
constexpr auto op_dispatch = dsl::op(dsl::lit_c<'.'>);
struct dispatch_op : dsl::infix_op_left {
static constexpr auto op = op_dispatch;
using operand = dsl::atom;
};
using operation = dispatch_op;
static constexpr auto value = lexy::fold_inplace<std::unique_ptr<ast::function_call_expression>>(
[] { return std::make_unique<ast::function_call_expression>(); }, // create an empty object
[](auto& node, std::string name) { node->name = (LEXY_MOV(name)); }, // when passed a string, assign it to the name
[](auto& node, ast::expression_pointer opr) { node->operands.push_back(LEXY_MOV(opr)); }) >> // when passed an expression pointer of any type, add it to the vector
lexy::callback(
lexy::forward<ast::expression_pointer>, // paren_expr
lexy::new_<ast::integer_expression, ast::expression_pointer>, // literal
lexy::new_<ast::string_constant_expression, ast::expression_pointer>, // literal
lexy::new_<ast::boolean_constant_expression, ast::expression_pointer>, // literal
lexy::new_<ast::identifier_expression, ast::expression_pointer> // identifier
);
}; However, this I was thinking that I would be able to use the expression parser here to convert the simple types ( And when it comes to the more complex operations with multiple nested expressions, or optional lists of expressions I feel like I flat out have no idea how to handle them. For example, a let statement looks like: expr := ID <- expr
...
| let ID : TYPE [<- expr] ⟦, ID : TYPE [<- expr]⟧* in expr
...
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 6 replies
-
Yes, you can't use fold_inplace here as you're not having a list. You're only having a list if you use From a cursory glance, it appears you're missing a callback overload that creates a function call (it will generate an identifier followed by an expression_pointer, that is the "// I don't fully get.." line, which matches an identifier, optionally followed by a parenthesized expression (which btw never matches, as you're already parsing an identifier earlier)), and an overload for If you're having trouble with errors, I recommend that you split everything into separate production as far as possible. For example, a separate production for literals and names, etc. Depending on your compiler, the error message also lists you the production with the missing overload and the types it had (those are passed to
It's the same principle: you create further derived classes storing the operands, and an overload that constructs them. The trick is that lexy gives you the operators as well. So for example: |
Beta Was this translation helpful? Give feedback.
Yes, you can't use fold_inplace here as you're not having a list. You're only having a list if you use
dsl::infix_op_list
, so don't need a sink. This means the entirefold_inplace(...) >>
stuff is unnecessary, as that just specifies a sink to use.From a cursory glance, it appears you're missing a callback overload that creates a function call (it will generate an identifier followed by an expression_pointer, that is the "// I don't fully get.." line, which matc…