On Thu, Feb 22, 2024 at 2:17 AM Zach Laine
On Wed, Feb 21, 2024 at 12:27 PM Phil Endecott via Boost
wrote:
[snip]
One other advantage of having the lambda return a result could be improving how the semantic type (aka attribute type) of a rule is set. For example, if I have the rule
degrees >> minutes
where degrees and minutes return doubles, the semantic type of the combined rule is something like tuple
. But I'm going to add a semantic action that adds the two values as shown above, returning one double. If the return value of the semantic action's lambda were used as the semantic type of the rule then we could write: auto rule = (degrees >> minutes) [( [](auto d, auto m) { return d + m/60.0; } )];
and rule would return a double to its parent rule. As it is, the semantic action doesn't change the type and we have to explicitly and verbosely declare the rule as returning a double.
I had not thought of this before, but I really like it. I'll have to think pretty deeply about whether there is some reason this might not work, but I'm definitely going to try to implement this. I'm thinking that the rule would be: if std::apply(f, tup) is well-formed (where f is your invocable, and tup is your tuple of values returned by _attr()), and decltype(std::apply(f, tup)) is assignable to _val(), then do the semantic action as _val(ctx) = std::apply(f, _attr(ctx)); otherwise, the semantic action gets called with the current context.. There would be a special case when _attr() is not a tuple, but you get the idea. Ticket to track it: https://github.com/tzlaine/parser/issues/106
This is now implemented. The logic I came up with is this. In general, the return value of the semantic action can be used to assign the value that would normally be assigned to _val(ctx) in a semantic action, but after the semantic action is executed. So, now you can write a semantic action with a signature like void(auto&ctx) like before, or you can write one like T(auto&ctx), for some non-void type T. In the latter case, the returned T gets assigned to _val(ctx). You can also write a semantic action that takes the attribute and returns something assignable to _val(ctx), or even one that takes the individual elements of the attribute, if it is a tuple, and returns something assignable to _val(ctx).
From the updated docs:
" More formally, within a rule, the use of a semantic action is determined as follows. Assume we have a function APPLY that calls a function with the elements of a tuple, like std::apply. For some context ctx, semantic action action, and attribute attr, action is used like this: - _val(ctx) = action(std::move(attr)), if that is well-formed; - otherwise, _val(ctx) = APPLY(action, std::move(attr)), if that is well-formed; - otherwise, _val(ctx) = action(ctx), if that is well-formed; - otherwise, action(ctx). The first two cases do not pass the context to the action at all. The last case is the normal use of semantic actions outside of rules. " Zach