From: Alan T. DeKok Date: Thu, 1 May 2025 11:55:44 +0000 (-0400) Subject: add support for 'continue' X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fff7627820c063a8b1dd2f4a2702f6acc3ece666;p=thirdparty%2Ffreeradius-server.git add support for 'continue' along with documentation and tests --- diff --git a/doc/antora/modules/reference/nav.adoc b/doc/antora/modules/reference/nav.adoc index c8fdde3553..ead1e51d09 100644 --- a/doc/antora/modules/reference/nav.adoc +++ b/doc/antora/modules/reference/nav.adoc @@ -10,6 +10,7 @@ **** 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] diff --git a/doc/antora/modules/reference/pages/unlang/continue.adoc b/doc/antora/modules/reference/pages/unlang/continue.adoc new file mode 100644 index 0000000000..7c4ed2d5ad --- /dev/null +++ b/doc/antora/modules/reference/pages/unlang/continue.adoc @@ -0,0 +1,36 @@ += 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. diff --git a/doc/antora/modules/reference/pages/unlang/keywords.adoc b/doc/antora/modules/reference/pages/unlang/keywords.adoc index 945abd6498..09b02f2b6f 100644 --- a/doc/antora/modules/reference/pages/unlang/keywords.adoc +++ b/doc/antora/modules/reference/pages/unlang/keywords.adoc @@ -13,17 +13,18 @@ looping, etc. [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 diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index f396f06179..57518cc6cf 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -381,6 +381,7 @@ static void unlang_dump(unlang_t *instruction, int depth) break; case UNLANG_TYPE_BREAK: + case UNLANG_TYPE_CONTINUE: case UNLANG_TYPE_DETACH: case UNLANG_TYPE_RETURN: case UNLANG_TYPE_TMPL: @@ -3455,6 +3456,38 @@ static unlang_t *compile_break(unlang_t *parent, unlang_compile_t *unlang_ctx, C 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; @@ -4720,6 +4753,7 @@ static int unlang_section_keywords_len = NUM_ELEMENTS(unlang_section_keywords); 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 }, }; diff --git a/src/lib/unlang/foreach.c b/src/lib/unlang/foreach.c index 2f1e360a44..585da2d98b 100644 --- a/src/lib/unlang/foreach.c +++ b/src/lib/unlang/foreach.c @@ -543,6 +543,26 @@ static unlang_action_t unlang_break(rlm_rcode_t *p_result, request_t *request, u 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, @@ -557,4 +577,10 @@ void unlang_foreach_init(void) .name = "break", .interpret = unlang_break, }); + + unlang_register(UNLANG_TYPE_CONTINUE, + &(unlang_op_t){ + .name = "continue", + .interpret = unlang_continue, + }); } diff --git a/src/lib/unlang/unlang_priv.h b/src/lib/unlang/unlang_priv.h index 18e1a95b5f..755552e81a 100644 --- a/src/lib/unlang/unlang_priv.h +++ b/src/lib/unlang/unlang_priv.h @@ -58,6 +58,7 @@ typedef enum { 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). @@ -416,6 +417,24 @@ static inline unsigned int unlang_frame_by_op_flag(unlang_stack_t *stack, unlang 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