]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Filter: Implement for loops
authorOndrej Zajicek (work) <santiago@crfreenet.org>
Mon, 14 Mar 2022 19:36:20 +0000 (20:36 +0100)
committerOndrej Zajicek <santiago@crfreenet.org>
Mon, 27 Jun 2022 19:13:32 +0000 (21:13 +0200)
For loops allow to iterate over elements in compound data like BGP paths
or community lists. The syntax is:

  for [ <type> ] <variable> in <expr> do <command-body>

doc/bird.sgml
filter/config.Y
filter/data.c
filter/f-inst.c
filter/test.conf
nest/a-path.c
nest/a-set.c
nest/attrs.h

index f933128c346b2772c7ca0493dddbeecb670947f6..89b1541cb0383633e690681148fc867793cdcf26 100644 (file)
@@ -1683,7 +1683,8 @@ prefix and an ASN as arguments.
 <sect>Control structures
 <label id="control-structures">
 
-<p>Filters support two control structures: conditions and case switches.
+<p>Filters support several control structures: conditions, for loops and case
+switches.
 
 <p>Syntax of a condition is: <cf>if <M>boolean expression</M> then <m/commandT/;
 else <m/commandF/;</cf> and you can use <cf>{ <m/command1/; <m/command2/;
@@ -1691,6 +1692,14 @@ else <m/commandF/;</cf> and you can use <cf>{ <m/command1/; <m/command2/;
 omitted. If the <cf><m>boolean expression</m></cf> is true, <m/commandT/ is
 executed, otherwise <m/commandF/ is executed.
 
+<p>For loops allow to iterate over elements in compound data like BGP paths or
+community lists. The syntax is: <cf>for [ <m/type/ ] <m/variable/ in <m/expr/
+do <m/command/;</cf> and you can also use compound command like in conditions.
+The expression is evaluated to a compound data, then for each element from such
+data the command is executed with the item assigned to the variable. A variable
+may be an existing one (when just name is used) or a locally defined (when type
+and name is used). In both cases, it must have the same type as elements.
+
 <p>The <cf>case</cf> is similar to case from Pascal. Syntax is <cf>case
 <m/expr/ { else: | <m/num_or_prefix [ .. num_or_prefix]/: <m/statement/ ; [
 ... ] }</cf>. The expression after <cf>case</cf> can be of any type which can be
@@ -1703,16 +1712,21 @@ neither of the <cf/:/ clauses, the statements after <cf/else:/ are executed.
 <p>Here is example that uses <cf/if/ and <cf/case/ structures:
 
 <code>
+if 1234 = i then printn "."; else {
+       print "not 1234";
+       print "You need {} around multiple commands";
+}
+
+for int asn in bgp_path do {
+       printn "ASN: ", asn;
+       if asn < 65536 then print " (2B)"; else print " (4B)";
+}
+
 case arg1 {
        2: print "two"; print "I can do more commands without {}";
        3 .. 5: print "three to five";
        else: print "something else";
 }
-
-if 1234 = i then printn "."; else {
-  print "not 1234";
-  print "You need {} around multiple commands";
-}
 </code>
 
 
index f8f47862a073e483db2e3159b02493019b4418c5..c7c82068bb1ff82ee9b0bdfea05556304e2cb28b 100644 (file)
@@ -296,7 +296,7 @@ assert_assign(struct f_lval *lval, struct f_inst *expr, const char *start, const
 
   checker = f_new_inst(FI_EQ, expr, getter);
   setter->next = checker;
-  
+
   return assert_done(setter, start, end);
 }
 
@@ -307,6 +307,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
        INT, BOOL, IP, TYPE, PREFIX, RD, PAIR, QUAD, EC, LC,
        SET, STRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
        IF, THEN, ELSE, CASE,
+       FOR, IN, DO,
        TRUE, FALSE, RT, RO, UNKNOWN, GENERIC,
        FROM, GW, NET, MASK, PROTO, SOURCE, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS,
        PREFERENCE,
@@ -342,6 +343,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
 %type <v> set_atom switch_atom fipa
 %type <px> fprefix
 %type <t> get_cf_position
+%type <s> for_var
 
 CF_GRAMMAR
 
@@ -899,6 +901,11 @@ var:
      $$ = f_new_inst(FI_VAR_INIT, $3, sym);
    }
 
+for_var:
+   type symbol { $$ = cf_define_symbol($2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope)); }
+ | CF_SYM_KNOWN { $$ = $1; cf_assert_symbol($1, SYM_VARIABLE); }
+ ;
+
 cmd:
    '{' cmds_scoped '}' {
      $$ = $2;
@@ -909,6 +916,18 @@ cmd:
  | IF term THEN cmd ELSE cmd {
      $$ = f_new_inst(FI_CONDITION, $2, $4, $6);
    }
+ | FOR {
+     /* Reserve space for walk data on stack */
+     cf_push_scope(NULL);
+     conf_this_scope->slots += 2;
+   } for_var IN
+   /* Parse term in the parent scope */
+   { conf_this_scope->active = 0; } term { conf_this_scope->active = 1; }
+   DO cmd {
+     cf_pop_scope();
+     $$ = f_new_inst(FI_FOR_INIT, $6, $3);
+     $$->next = f_new_inst(FI_FOR_NEXT, $3, $9);
+   }
  | CF_SYM_KNOWN '=' term ';' {
      switch ($1->class) {
        case SYM_VARIABLE_RANGE:
index 2bb5920cf0da220f0ba620852c935205c3e6945f..87c438fcf9d1f07d5d21b3468a56202b7b0afd48 100644 (file)
@@ -72,6 +72,7 @@ enum f_type
 f_type_element_type(enum f_type t)
 {
   switch(t) {
+    case T_PATH:   return T_INT;
     case T_CLIST:  return T_PAIR;
     case T_ECLIST: return T_EC;
     case T_LCLIST: return T_LC;
index dc243b4e5190af8fef1f4db2d37c849cb87d89b1..30db96f28e269bb584e8b5767b27f33a869e6d1c 100644 (file)
  *     m4_dnl    RESULT_VOID;                          return undef
  *     m4_dnl  }
  *
+ *     Note that runtime arguments m4_dnl (ARG*, VARARG) must be defined before
+ *     parse-time arguments m4_dnl (LINE, SYMBOL, ...). During linearization,
+ *     first ones move position in f_line by linearizing arguments first, while
+ *     second ones store data to the current position.
+ *
  *     Also note that the { ... } blocks are not respected by M4 at all.
  *     If you get weird unmatched-brace-pair errors, check what it generated and why.
  *     What is really considered as one instruction is not the { ... } block
     RESULT_VAL(val);
   }
 
+  INST(FI_FOR_INIT, 1, 0) {
+    NEVER_CONSTANT;
+    ARG_ANY(1);
+    SYMBOL;
+
+    FID_NEW_BODY()
+    ASSERT((sym->class & ~0xff) == SYM_VARIABLE);
+
+    /* Static type check */
+    if (f1->type)
+    {
+      enum f_type t_var = (sym->class & 0xff);
+      enum f_type t_arg = f_type_element_type(f1->type);
+      if (!t_arg)
+        cf_error("Value of expression in FOR must be iterable, got %s",
+                f_type_name(f1->type));
+      if (t_var != t_arg)
+       cf_error("Loop variable '%s' in FOR must be %s, is %s",
+                sym->name, f_type_name(t_arg), f_type_name(t_var));
+    }
+
+    FID_INTERPRET_BODY()
+
+    /* Dynamic type check */
+    if ((sym->class & 0xff) != f_type_element_type(v1.type))
+      runtime("Mismatched argument and variable type");
+
+    /* Setup the index */
+    v2 = (struct f_val) { .type = T_INT, .val.i = 0 };
+
+    /* Keep v1 and v2 on the stack */
+    fstk->vcnt += 2;
+  }
+
+  INST(FI_FOR_NEXT, 2, 0) {
+    NEVER_CONSTANT;
+    SYMBOL;
+
+    /* Type checks are done in FI_FOR_INIT */
+
+    /* Loop variable */
+    struct f_val *var = &fstk->vstk[curline.vbase + sym->offset];
+    int step = 0;
+
+    switch(v1.type)
+    {
+    case T_PATH:
+      var->type = T_INT;
+      step = as_path_walk(v1.val.ad, &v2.val.i, &var->val.i);
+      break;
+
+    case T_CLIST:
+      var->type = T_PAIR;
+      step = int_set_walk(v1.val.ad, &v2.val.i, &var->val.i);
+      break;
+
+    case T_ECLIST:
+      var->type = T_EC;
+      step = ec_set_walk(v1.val.ad, &v2.val.i, &var->val.ec);
+      break;
+
+    case T_LCLIST:
+      var->type = T_LC;
+      step = lc_set_walk(v1.val.ad, &v2.val.i, &var->val.lc);
+      break;
+
+    default:
+      runtime( "Clist or lclist expected" );
+    }
+
+    if (step)
+    {
+      /* Keep v1 and v2 on the stack */
+      fstk->vcnt += 2;
+
+      /* Repeat this instruction */
+      curline.pos--;
+
+      /* Execute the loop body */
+      LINE(1, 0);
+
+      /* Space for loop variable, may be unused */
+      fstk->vcnt += 1;
+    }
+    else
+      var->type = T_VOID;
+  }
+
   INST(FI_CONDITION, 1, 0) {
     ARG(1, T_BOOL);
     if (v1.val.i)
index 436031a3cc50ed45e42a0aa777bad2894fd83be2..bc5f898e11388cf24a6d05c848e02ce567336238 100644 (file)
@@ -758,6 +758,15 @@ int set set12;
        bt_assert(delete(p2, [4..5]) = prepend(prepend(prepend(prepend(+empty+, 3), 3), 2), 1));
 
        bt_assert(format([= 1 2+ 3 =]) = "[= 1 2 + 3 =]");
+
+       # iteration over path
+       int x = 0;
+       int y = 0;
+       for int i in p2 do {
+           x = x + i;
+           y = y + x;
+       }
+       bt_assert(x = 18 && y = 50);
 }
 
 bt_test_suite(t_path, "Testing paths");
@@ -884,6 +893,12 @@ clist r;
        bt_assert(format(r) = "(clist (2,1) (1,3) (2,2) (3,1) (2,3))");
        bt_assert(r.min = (1,3));
        bt_assert(r.max = (3,1));
+
+       # iteration over clist
+       int x = 0;
+       for pair c in r do
+           x = x + c.asn * c.asn * c.data;
+       bt_assert(x = 36);
 }
 
 bt_test_suite(t_clist, "Testing lists of communities");
@@ -999,6 +1014,13 @@ eclist r;
        bt_assert(format(r) = "(eclist (rt, 2, 1) (rt, 1, 3) (rt, 2, 2) (rt, 3, 1) (rt, 2, 3))");
        bt_assert(r.min = (rt, 1, 3));
        bt_assert(r.max = (rt, 3, 1));
+
+       # iteration over eclist
+       int x = 0;
+       for ec c in r do
+         if c > (rt, 2, 0) && c < (rt, 3, 0) then
+           x = x + 1;
+       bt_assert(x = 3);
 }
 
 bt_test_suite(t_eclist, "Testing lists of extended communities");
@@ -1117,6 +1139,19 @@ lclist r;
        bt_assert(format(r) = "(lclist (2, 3, 3) (1, 2, 3) (2, 3, 1) (3, 1, 2) (2, 1, 3))");
        bt_assert(r.min = (1, 2, 3));
        bt_assert(r.max = (3, 1, 2));
+
+       # iteration over lclist
+       int x = 0;
+       int y = 0;
+       lc mx = (0, 0, 0);
+       for lc c in r do {
+           int asn2 = c.asn * c.asn;
+           x = x + asn2 * c.data1;
+           y = y + asn2 * c.data2;
+           if c > mx then mx = c;
+       }
+       bt_assert(x = 39 && y = 49);
+       bt_assert(mx = r.max);
 }
 
 bt_test_suite(t_lclist, "Testing lists of large communities");
@@ -1580,13 +1615,16 @@ filter vpn_filter
        bt_assert(net.type != NET_IP6);
        bt_assert(net.rd = 0:1:2);
 
+       bool b = false;
        case (net.type) {
          NET_IP4: print "IPV4";
          NET_IP6: print "IPV6";
+         else: b = true;
        }
+       bt_assert(b);
 
        bt_check_assign(from, 10.20.30.40);
-       bt_check_assign(gw, 55.55.55.44);
+       bt_check_assign(gw, 55.55.55.44);
 
        bgp_community.add((3,5));
        bgp_ext_community.add((ro, 135, 999));
index d5b01635e9d22b1b177dab0dff4862bd75331f54..6bb1828592ffba165d76098c72c03ca2f34e9d07 100644 (file)
@@ -669,6 +669,35 @@ as_path_filter(struct linpool *pool, const struct adata *path, const struct f_va
   return res;
 }
 
+int
+as_path_walk(const struct adata *path, uint *pos, uint *val)
+{
+  if (!path)
+    return 0;
+
+  const u8 *p = path->data;
+  const u8 *q = p + path->length;
+  uint n, x = *pos;
+
+  while (p < q)
+  {
+    n = p[1];
+    p += 2;
+
+    if (x < n)
+    {
+      *val = get_as(p + x * BS);
+      *pos += 1;
+      return 1;
+    }
+
+    p += n * BS;
+    x -= n;
+  }
+
+  return 0;
+}
+
 
 struct pm_pos
 {
index 71fbac946b3013d68be44156806321ad6f39c4d9..40ed573cf859a506ce555eb67f5be7c8d81b8eb1 100644 (file)
@@ -693,3 +693,51 @@ lc_set_max(const struct adata *list, lcomm *val)
   *val = (lcomm) { res[0], res[1], res[2] };
   return 1;
 }
+
+int
+int_set_walk(const struct adata *list, uint *pos, uint *val)
+{
+  if (!list)
+    return 0;
+
+  if (*pos >= (uint) int_set_get_size(list))
+    return 0;
+
+  u32 *res = int_set_get_data(list) + *pos;
+  *val = *res;
+  *pos += 1;
+
+  return 1;
+}
+
+int
+ec_set_walk(const struct adata *list, uint *pos, u64 *val)
+{
+  if (!list)
+    return 0;
+
+  if (*pos >= (uint) int_set_get_size(list))
+    return 0;
+
+  u32 *res = int_set_get_data(list) + *pos;
+  *val = ec_generic(res[0], res[1]);
+  *pos += 2;
+
+  return 1;
+}
+
+int
+lc_set_walk(const struct adata *list, uint *pos, lcomm *val)
+{
+  if (!list)
+    return 0;
+
+  if (*pos >= (uint) int_set_get_size(list))
+    return 0;
+
+  u32 *res = int_set_get_data(list) + *pos;
+  *val = (lcomm) { res[0], res[1], res[2] };
+  *pos += 3;
+
+  return 1;
+}
index 22e2ff4aa2f5a6551b6c5a94dd0be80017b3ab6f..9412439bf4a9e07141deb172fafa567515b8ec68 100644 (file)
@@ -51,6 +51,7 @@ u32 as_path_get_last_nonaggregated(const struct adata *path);
 int as_path_contains(const struct adata *path, u32 as, int min);
 int as_path_match_set(const struct adata *path, const struct f_tree *set);
 const struct adata *as_path_filter(struct linpool *pool, const struct adata *path, const struct f_val *set, int pos);
+int as_path_walk(const struct adata *path, uint *pos, uint *val);
 
 static inline struct adata *as_path_prepend(struct linpool *pool, const struct adata *path, u32 as)
 { return as_path_prepend2(pool, path, AS_PATH_SEQUENCE, as); }
@@ -225,6 +226,9 @@ int lc_set_min(const struct adata *list, lcomm *val);
 int int_set_max(const struct adata *list, u32 *val);
 int ec_set_max(const struct adata *list, u64 *val);
 int lc_set_max(const struct adata *list, lcomm *val);
+int int_set_walk(const struct adata *list, uint *pos, u32 *val);
+int ec_set_walk(const struct adata *list, uint *pos, u64 *val);
+int lc_set_walk(const struct adata *list, uint *pos, lcomm *val);
 
 void ec_set_sort_x(struct adata *set); /* Sort in place */