*
*/
typedef struct {
- bool needs_resolving;//!< Needs pass2 resolution.
- bool pure; //!< has no external side effects, true for BOX, LITERAL, and some functions
- bool impure_func; //!< xlat contains an impure function
- bool can_purify; //!< if the xlat has a pure function with pure arguments.
+ uint8_t needs_resolving : 1; //!< Needs pass2 resolution.
+ uint8_t pure : 1; //!< has no external side effects, true for BOX, LITERAL, and some functions
+ uint8_t impure_func : 1; //!< xlat contains an impure function
+ uint8_t can_purify : 1; //!< if the xlat has a pure function with pure arguments.
- bool constant; //!< xlat is just tmpl_attr_tail_data, or XLAT_BOX
- bool xlat; //!< it's an xlat wrapper
+ uint8_t constant : 1; //!< xlat is just tmpl_attr_tail_data, or XLAT_BOX
+ uint8_t xlat : 1; //!< it's an xlat wrapper
} xlat_flags_t;
extern fr_table_num_sorted_t const xlat_action_table[];
XLAT_DEBUG("** [%i] %s(child) - continuing %%{%s ...}", unlang_interpret_stack_depth(request), __FUNCTION__,
node->fmt);
+ /*
+ * Hoist %{...} to its results.
+ *
+ * There may be zero or more results.
+ */
+ if (node->hoist) {
+ while ((arg = fr_value_box_list_pop_head(result)) != NULL) {
+ talloc_steal(ctx, arg);
+ fr_dcursor_insert(out, arg);
+ }
+ break;
+ }
+
MEM(arg = fr_value_box_alloc(ctx, FR_TYPE_GROUP, NULL));
if (!fr_value_box_list_empty(result)) {
char *escaped;
/*
- * Groups only come about because of quoted strings.
+ * Groups commonly are because of quoted strings.
+ *
+ * However, we sometimes have a group because of %{...}, in which case the result is just
+ * a leaf value.
*/
- if (node->type == XLAT_GROUP) {
- fr_assert(vb->type == FR_TYPE_GROUP);
+ if ((node->type == XLAT_GROUP) && (vb->type == FR_TYPE_GROUP)) {
fr_assert(node->quote != T_BARE_WORD);
if (xlat_sync_stringify(vb, request, node->group, &vb->vb_group, escape, escape_ctx) < 0) return -1;
/*
* It must be an expression.
*
- * We wrap the xlat in a tmpl, so that the result is just a value, and not wrapped in another
- * XLAT_GROUP, which turns into a wrapper of FR_TYPE_GROUP in the value-box.
+ * We wrap the xlat in a group, and then mark the group to be hoisted.
*/
{
- xlat_exp_head_t *child;
tmpl_rules_t my_rules;
fr_sbuff_set(in, &m_s); /* backtrack to the start of the expression */
- MEM(node = xlat_exp_alloc(head, XLAT_TMPL, NULL, 0));
- MEM(node->vpt = tmpl_alloc(node, TMPL_TYPE_XLAT, T_BARE_WORD, "", 1));
+ MEM(node = xlat_exp_alloc(head, XLAT_GROUP, NULL, 0));
if (t_rules) {
my_rules = *t_rules;
t_rules = &my_rules;
}
- ret = xlat_tokenize_expression(node->vpt, &child, in, &attr_p_rules, t_rules);
+ ret = xlat_tokenize_expression(node, &node->group, in, &attr_p_rules, t_rules);
if (ret <= 0) {
talloc_free(node);
return ret;
}
xlat_exp_set_name(node, fr_sbuff_current(&m_s), fr_sbuff_behind(&m_s));
- tmpl_set_name_shallow(node->vpt, T_BARE_WORD, node->fmt, fr_sbuff_behind(&m_s));
+ node->flags = node->group->flags;
- tmpl_set_xlat(node->vpt, child);
- xlat_exp_insert_tail(head, node);
+ /*
+ * Print it as %{...}. Then when we're evaluating a string, hoist the results.
+ */
+ node->flags.xlat = true;
+ node->hoist = true;
- child->flags.xlat = true;
- node->flags = child->flags;
- fr_assert(tmpl_xlat(node->vpt) != NULL);
+ xlat_exp_insert_tail(head, node);
(void) fr_sbuff_next(in); /* skip '}' */
return ret;
if (!node) return 0;
+ if (node->flags.xlat) FR_SBUFF_IN_CHAR_RETURN(out, '%', '{');
+
switch (node->type) {
case XLAT_GROUP:
if (node->quote != T_BARE_WORD) FR_SBUFF_IN_CHAR_RETURN(out, fr_token_quote[node->quote]);
if (xlat_exp_next(head, node)) {
if (c) FR_SBUFF_IN_CHAR_RETURN(out, c);
- FR_SBUFF_IN_CHAR_RETURN(out, ' '); /* Add ' ' between args */
+
+ /*
+ * This thing is %{...}, and we don't print a space between the last argument
+ * and the '}'.
+ */
+ if (!node->flags.xlat) FR_SBUFF_IN_CHAR_RETURN(out, ' '); /* Add ' ' between args */
}
goto done;
if (tmpl_contains_xlat(node->vpt)) { /* xlat and exec */
if (node->vpt->quote == T_BARE_WORD) {
- if (node->flags.xlat) FR_SBUFF_IN_CHAR_RETURN(out, '%', '{');
xlat_print(out, tmpl_xlat(node->vpt), NULL);
- if (node->flags.xlat) FR_SBUFF_IN_CHAR_RETURN(out, '}');
} else {
FR_SBUFF_IN_CHAR_RETURN(out, fr_token_quote[node->vpt->quote]);
xlat_print(out, tmpl_xlat(node->vpt), fr_value_escape_by_quote[node->quote]);
FR_SBUFF_IN_CHAR_RETURN(out, close);
done:
+ if (node->flags.xlat) FR_SBUFF_IN_CHAR_RETURN(out, '}');
+
return fr_sbuff_used_total(out) - at_in;
}
*/
if (tmpl_resolve(node->vpt, xr_rules->tr_rules) < 0) return -1;
+ if (xlat_tmpl_normalize(node) < 0) return -1;
+
node->flags.needs_resolving = false;
node->flags.pure = tmpl_is_data(node->vpt);
break;
# First choice
#
result_string := "%{Filter-Id || NAS-Identifier}"
-if (!(result_string == 'foo')) {
+if (result_string != 'foo') {
test_fail
}
#
request -= Filter-Id[*]
result_string := "%{Filter-Id || NAS-Identifier}"
-if (!(result_string == 'bar')) {
+if (result_string != 'bar') {
test_fail
}
# Multiple things in an alternation
#
result_string := %{%{Filter-Id} || "%{NAS-Identifier} foo"}
-if (!(result_string == 'bar foo')) {
+if (result_string != 'bar foo') {
test_fail
}
# Alternation is empty
#
result_string := "%{Filter-Id || ''}"
-if (!(result_string == '')) {
+if (result_string != '') {
test_fail
}
# Both sides are failing, so the assignment returns a NULL string
#
result_string := "%{Filter-Id || NAS-Identifier}"
-if (!(result_string == "")) {
+if (result_string !='') {
test_fail
}