**** xref:unlang/caller.adoc[caller]
**** xref:unlang/case.adoc[case]
**** xref:unlang/catch.adoc[catch]
+**** xref:unlang/continue.adoc[continue]
**** xref:unlang/default.adoc[default]
**** xref:unlang/detach.adoc[detach]
**** xref:unlang/edit.adoc[editing]
--- /dev/null
+= The continue statement
+
+.Syntax
+[source,unlang]
+----
+continue
+----
+
+The `continue` statement is used in a
+xref:unlang/foreach.adoc[foreach] loop, and will cause the interpret
+to skip execution of the rest of the statements in current loop
+iteration, and start the next iteration of the loop. The `continue`
+statement cannot be used in any other location.
+
+In this example, a `continue` is used to exit a
+xref:unlang/foreach.adoc[foreach] loop when a particular condition
+matches.
+
+.Example of continue
+[source,unlang]
+----
+uint32 last
+
+foreach i (%range(10)) {
+ if (i < 8) {
+ last := i
+ continue
+ }
+
+ break
+}
+----
+
+
+// Copyright (C) 2025 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// This documentation was developed by Network RADIUS SAS.
[cols="30%,70%"]
|=====
| Keyword | Description
-| xref:unlang/break.adoc[break] | Exit early from a `foreach` loop.
-| xref:unlang/case.adoc[case] | Match inside of a `switch`.
+| xref:unlang/break.adoc[break] | Exit early from a xref:unlang/foreach.adoc[foreach] loop.
+| xref:unlang/case.adoc[case] | Match inside of a xref:unlang/switch.adoc[switch].
| xref:unlang/catch.adoc[catch] | catch an error from a previous "try"
-| xref:unlang/else.adoc[else] | Do something when an `if` does not match.
-| xref:unlang/elsif.adoc[elsif] | Check for condition when a previous `if` does not match.
+| xref:unlang/continue.adoc[continue] | Start the next iteration of a xref:unlang/foreach.adoc[foreach] loop
+| xref:unlang/else.adoc[else] | Do something when a previous xref:unlang/if.adoc[if] does not match.
+| xref:unlang/elsif.adoc[elsif] | Check for condition when a previous xref:unlang/if.adoc[if] does not match.
| xref:unlang/foreach.adoc[foreach] | Loop over a list of attributes.
| xref:unlang/if.adoc[if] | Check for a condition, and execute a sub-policy if it matches.
| xref:unlang/return.adoc[return] | Immediately stop processing a section.
| xref:unlang/switch.adoc[switch] | Check for multiple values.
| xref:unlang/transaction.adoc[transaction] | Group operations into a transaction which can be reverted on failure.
-| xref:unlang/try.adoc[try] | try / catch blocks
+| xref:unlang/try.adoc[try] | Perform an operation, and ref:unlang/catch.adoc[catch] an error if it fails.
|=====
== Attribute Editing Keywords
break;
case UNLANG_TYPE_BREAK:
+ case UNLANG_TYPE_CONTINUE:
case UNLANG_TYPE_DETACH:
case UNLANG_TYPE_RETURN:
case UNLANG_TYPE_TMPL:
return compile_empty(parent, unlang_ctx, NULL, &break_ext);
}
+static unlang_t *compile_continue(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
+{
+ unlang_t *unlang;
+
+ static unlang_ext_t const break_ext = {
+ .type = UNLANG_TYPE_CONTINUE,
+ .len = sizeof(unlang_group_t),
+ .type_name = "unlang_group_t",
+ };
+
+ for (unlang = parent; unlang != NULL; unlang = unlang->parent) {
+ /*
+ * "continue" doesn't go past a return point.
+ */
+ if ((unlang_ops[unlang->type].flag & UNLANG_OP_FLAG_RETURN_POINT) != 0) goto error;
+
+ if (unlang->type == UNLANG_TYPE_FOREACH) break;
+ }
+
+ if (!unlang) {
+ error:
+ cf_log_err(ci, "Invalid location for 'continue' - it can only be used inside 'foreach'");
+ cf_log_err(ci, DOC_KEYWORD_REF(break));
+ return NULL;
+ }
+
+ parent->closed = true;
+
+ return compile_empty(parent, unlang_ctx, NULL, &break_ext);
+}
+
+
static unlang_t *compile_detach(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
{
unlang_t *subrequest;
static fr_table_ptr_sorted_t unlang_pair_keywords[] = {
{ L("break"), (void *) compile_break },
+ { L("continue"), (void *) compile_continue },
{ L("detach"), (void *) compile_detach },
{ L("return"), (void *) compile_return },
};
return ua;
}
+static unlang_action_t unlang_continue(UNUSED rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
+{
+ unlang_stack_t *stack = request->stack;
+ unsigned int depth;
+
+ RDEBUG2("%s", unlang_ops[frame->instruction->type].name);
+
+ depth = unlang_frame_by_type(stack, UNLANG_TYPE_FOREACH);
+ fr_assert(depth != 0); /* this would be a problem with the compile phase */
+
+ /*
+ * Unwind to the child, and mark the child as "nope, we're not repeating it". This will cause
+ * the interpreter to go to the next child.
+ */
+ unwind_to_depth(stack, depth + 1);
+ repeatable_clear(&stack->frame[depth + 1]);
+
+ return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
void unlang_foreach_init(void)
{
unlang_register(UNLANG_TYPE_FOREACH,
.name = "break",
.interpret = unlang_break,
});
+
+ unlang_register(UNLANG_TYPE_CONTINUE,
+ &(unlang_op_t){
+ .name = "continue",
+ .interpret = unlang_continue,
+ });
}
UNLANG_TYPE_CASE, //!< Case section (within a #UNLANG_TYPE_SWITCH).
UNLANG_TYPE_FOREACH, //!< Foreach section.
UNLANG_TYPE_BREAK, //!< Break statement (within a #UNLANG_TYPE_FOREACH or #UNLANG_TYPE_CASE).
+ UNLANG_TYPE_CONTINUE, //!< Break statement (within a #UNLANG_TYPE_FOREACH).
UNLANG_TYPE_RETURN, //!< Return statement.
UNLANG_TYPE_MAP, //!< Mapping section (like #UNLANG_TYPE_UPDATE, but uses
//!< values from a #map_proc_t call).
return 0;
}
+/** Find the first frame with a given instruction
+ *
+ * @return
+ * - 0 if no frame has the op.
+ * - The index of the first frame with the op.
+ */
+static inline unsigned int unlang_frame_by_type(unlang_stack_t *stack, unlang_type_t op)
+{
+ unsigned int i;
+
+ for (i = stack->depth; i > 0; i--) {
+ unlang_stack_frame_t *frame = &stack->frame[i];
+
+ if (frame->instruction->type == op) return i;
+ }
+ return 0;
+}
+
/** Mark up frames as cancelled so they're immediately popped by the interpreter
*
* @note We used to do this asynchronously, but now we may need to execute timeout sections