]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add support for 'continue'
authorAlan T. DeKok <aland@freeradius.org>
Thu, 1 May 2025 11:55:44 +0000 (07:55 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 1 May 2025 11:55:44 +0000 (07:55 -0400)
along with documentation and tests

doc/antora/modules/reference/nav.adoc
doc/antora/modules/reference/pages/unlang/continue.adoc [new file with mode: 0644]
doc/antora/modules/reference/pages/unlang/keywords.adoc
src/lib/unlang/compile.c
src/lib/unlang/foreach.c
src/lib/unlang/unlang_priv.h

index c8fdde3553d3af2f81b06157cdc12ad61249e1d6..ead1e51d09f0848c7c7a0cee11a1f42860714d68 100644 (file)
@@ -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 (file)
index 0000000..7c4ed2d
--- /dev/null
@@ -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.
index 945abd6498cc17ea49793faf6b2c47538cee2fd6..09b02f2b6fd71bdf1a0a9636c7e61a647f66e374 100644 (file)
@@ -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
index f396f0617969e7cea12be27b6fb27b2aba983534..57518cc6cf5ffdeeba9a46ce55ba98de2ef0e04b 100644 (file)
@@ -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 },
 };
index 2f1e360a444f518773138e0b99a66a5e2ec0ad34..585da2d98b45c030a71743403f5ab82692ea6317 100644 (file)
@@ -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,
+                          });
 }
index 18e1a95b5f4e296aa30a55ad25a9ee9380898ebb..755552e81af5ae2d00ccdeec2e61e08a3f3053fb 100644 (file)
@@ -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