]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Filter: Methods rework
authorMaria Matejka <mq@ucw.cz>
Fri, 16 Jun 2023 15:35:37 +0000 (17:35 +0200)
committerOndrej Zajicek <santiago@crfreenet.org>
Tue, 12 Sep 2023 14:19:33 +0000 (16:19 +0200)
Methods can now be called as x.m(y), as long as x can have its type
inferred in config time. If used as a command, it modifies the object,
if used as a value, it keeps the original object intact.

Also functions add(x,y), delete(x,y), filter(x,y) and prepend(x,y) now
spit a warning and are considered deprecated.

It's also possible to call a method on a constant, see filter/test.conf
for examples like bgp_path = +empty+.prepend(1).

Inside instruction definitions (filter/f-inst.c), a METHOD_CONSTRUCTOR()
call is added, which registers the instruction as a method for the type
of its first argument. Each type has its own method symbol table and
filter parser switches between them based on the inferred type of the
object calling the method.

Also FI_CLIST_(ADD|DELETE|FILTER) instructions have been split to allow
for this method dispatch. With type inference, it's now possible.

conf/cf-lex.l
conf/conf.h
conf/confbase.Y
conf/gen_keywords.m4
doc/bird.sgml
filter/config.Y
filter/data.c
filter/data.h
filter/decl.m4
filter/f-inst.c
filter/test.conf

index 9e52417ac4eaed450c61716ec39a02bdca29b4d1..c4760e4037993741f9cac1f11de2a2afa1380951 100644 (file)
@@ -54,7 +54,6 @@
 struct keyword {
   byte *name;
   int value;
-  enum keyword_scope scope;
 };
 
 #include "conf/keywords.h"
@@ -80,7 +79,8 @@ static uint cf_hash(const byte *c);
 HASH_DEFINE_REHASH_FN(SYM, struct symbol)
 
 struct sym_scope *global_root_scope;
-static pool *global_root_scope_pool;
+pool *global_root_scope_pool;
+linpool *global_root_scope_linpool;
 
 linpool *cfg_mem;
 
@@ -557,7 +557,7 @@ check_eof(void)
 
 static inline void cf_swap_soft_scope(struct config *conf);
 
-static struct symbol *
+struct symbol *
 cf_new_symbol(struct sym_scope *scope, pool *p, struct linpool *lp, const byte *c)
 {
   if (scope->readonly)
@@ -686,6 +686,8 @@ cf_lex_symbol(const char *data)
       cf_lval.i = -val;
       return ENUM;
     }
+    case SYM_METHOD:
+      return sym->method->arg_num ? CF_SYM_METHOD_ARGS : CF_SYM_METHOD_BARE;
     case SYM_VOID:
       return CF_SYM_UNDEFINED;
     default:
@@ -693,6 +695,8 @@ cf_lex_symbol(const char *data)
   }
 }
 
+void f_type_methods_register(void);
+
 /**
  * cf_lex_init - initialize the lexer
  * @is_cli: true if we're going to parse CLI command, false for configuration
@@ -707,18 +711,19 @@ cf_lex_init(int is_cli, struct config *c)
   if (!global_root_scope_pool)
   {
     global_root_scope_pool = rp_new(&root_pool, "Keywords pool");
-    linpool *kwlp = lp_new(global_root_scope_pool);
-    global_root_scope = lp_allocz(kwlp, sizeof(*global_root_scope) * CFK__MAX);
+    global_root_scope_linpool = lp_new(global_root_scope_pool);
+    global_root_scope = lp_allocz(global_root_scope_linpool, sizeof(*global_root_scope));
 
     for (const struct keyword *k = keyword_list; k->name; k++)
     {
-      struct symbol *sym = cf_new_symbol(&global_root_scope[k->scope], global_root_scope_pool, kwlp, k->name);
+      struct symbol *sym = cf_new_symbol(global_root_scope, global_root_scope_pool, global_root_scope_linpool, k->name);
       sym->class = SYM_KEYWORD;
       sym->keyword = k;
     }
 
-    for (int s = 0; s < CFK__MAX; s++)
-      global_root_scope[s].readonly = 1;
+    global_root_scope->readonly = 1;
+
+    f_type_methods_register();
   }
 
   ifs_head = ifs = push_ifs(NULL);
@@ -742,7 +747,7 @@ cf_lex_init(int is_cli, struct config *c)
   if (is_cli)
     c->current_scope->next = config->root_scope;
   else
-    c->current_scope->next = &global_root_scope[CFK_KEYWORDS];
+    c->current_scope->next = global_root_scope;
 }
 
 /**
index b7c97ce5897f436ed04c5340007c3cf0c25a3e90..1413cb581bd09fff80ec7191e0bf38b4ce6a3cd0 100644 (file)
@@ -126,6 +126,7 @@ struct symbol {
     struct f_val *val;                 /* For SYM_CONSTANT */
     uint offset;                       /* For SYM_VARIABLE */
     const struct keyword *keyword;     /* For SYM_KEYWORD */
+    const struct f_method *method;     /* For SYM_METHOD */
   };
 
   char name[0];
@@ -144,13 +145,9 @@ struct sym_scope {
   byte readonly:1;                     /* Do not add new symbols */
 };
 
-enum keyword_scope {
-  CFK_KEYWORDS,
-  CFK_METHODS,
-  CFK__MAX
-};
-
 extern struct sym_scope *global_root_scope;
+extern pool *global_root_scope_pool;
+extern linpool *global_root_scope_linpool;
 
 struct bytestring {
   size_t length;
@@ -168,6 +165,7 @@ struct bytestring {
 #define SYM_TABLE 5
 #define SYM_ATTRIBUTE 6
 #define SYM_KEYWORD 7
+#define SYM_METHOD 8
 
 #define SYM_VARIABLE 0x100     /* 0x100-0x1ff are variable types */
 #define SYM_VARIABLE_RANGE SYM_VARIABLE ... (SYM_VARIABLE | 0xff)
@@ -215,6 +213,9 @@ struct symbol *cf_localize_symbol(struct config *conf, struct symbol *sym);
 static inline int cf_symbol_is_local(struct config *conf, struct symbol *sym)
 { return (sym->scope == conf->current_scope) && !conf->current_scope->soft_scopes; }
 
+/* internal */
+struct symbol *cf_new_symbol(struct sym_scope *scope, pool *p, struct linpool *lp, const byte *c);
+
 /**
  * cf_define_symbol - define meaning of a symbol
  * @sym: symbol to be defined
index e10666f8ba1cfdeae9b6824ea4d6e0e984a4aef1..c62731b592a078dbee055ce9bcb21f8bbfc72886 100644 (file)
@@ -104,7 +104,7 @@ CF_DECLS
 %token <ip4> IP4
 %token <ip6> IP6
 %token <i64> VPN_RD
-%token <s> CF_SYM_KNOWN CF_SYM_UNDEFINED
+%token <s> CF_SYM_KNOWN CF_SYM_UNDEFINED CF_SYM_METHOD_BARE CF_SYM_METHOD_ARGS
 %token <t> TEXT
 %token <bs> BYTETEXT
 %type <iface> ipa_scope
index 06a38ffd101f6186eb97ff3f7636955604c7cbe2..4e8651f6d426d3914594e66ef445c21e146cc488 100644 (file)
@@ -23,11 +23,10 @@ m4_define(CF_DECLS, `m4_divert(-1)')
 m4_define(CF_DEFINES, `m4_divert(-1)')
 
 # Keywords are translated to C initializers
-m4_define(CF_handle_kw, `m4_divert(1){ "m4_translit($1,[[A-Z]],[[a-z]])", $1, CF_keywd_target },
+m4_define(CF_handle_kw, `m4_divert(1){ "m4_translit($1,[[A-Z]],[[a-z]])", $1 },
 m4_divert(-1)')
-m4_define(CF_keywd, `m4_ifdef([[CF_tok_]]CF_keywd_target[[_$1]],,[[m4_define([[CF_tok_]]CF_keywd_target[[_$1]],1)CF_handle_kw($1)]])')
-m4_define(CF_KEYWORDS, `m4_define([[CF_keywd_target]],CFK_KEYWORDS)CF_iterate([[CF_keywd]], [[$@]])DNL')
-m4_define(CF_METHODS, `m4_define([[CF_keywd_target]],CFK_METHODS)CF_iterate([[CF_keywd]], [[$@]])DNL')
+m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)CF_handle_kw($1)]])')
+m4_define(CF_KEYWORDS, `CF_iterate([[CF_keywd]], [[$@]])DNL')
 m4_define(CF_KEYWORDS_EXCLUSIVE, `CF_KEYWORDS($@)')
 
 # CLI commands generate keywords as well
index 46e34c04f87f2133cd2453a6d3d08dcc62627bb0..3fcf0025d03e3ed8f05d9132a63df5708ef694bf 100644 (file)
@@ -1592,24 +1592,24 @@ in the foot).
 
        <cf><m/P/.len</cf> returns the length of path <m/P/.
 
-       <cf><m/P/.empty</cf> makes the path <m/P/ empty.
+       <cf><m/P/.empty</cf> makes the path <m/P/ empty. Can't be used as a value, always modifies the object.
 
-       <cf>prepend(<m/P/,<m/A/)</cf> prepends ASN <m/A/ to path <m/P/ and
+       <cf><m/P/.prepend(<m/A/)</cf> prepends ASN <m/A/ to path <m/P/ and
        returns the result.
 
-       <cf>delete(<m/P/,<m/A/)</cf> deletes all instances of ASN <m/A/ from
+       <cf><m/P/.delete(<m/A/)</cf> deletes all instances of ASN <m/A/ from
        from path <m/P/ and returns the result. <m/A/ may also be an integer
        set, in that case the operator deletes all ASNs from path <m/P/ that are
        also members of set <m/A/.
 
-       <cf>filter(<m/P/,<m/A/)</cf> deletes all ASNs from path <m/P/ that are
-       not members of integer set <m/A/. I.e., <cf/filter/ do the same as
-       <cf/delete/ with inverted set <m/A/.
+       <cf><m/P/.filter(<m/A/)</cf> deletes all ASNs from path <m/P/ that are
+       not members of integer set <m/A/, and returns the result.
+       I.e., <cf/filter/ do the same as <cf/delete/ with inverted set <m/A/.
 
-       Statement <cf><m/P/ = prepend(<m/P/, <m/A/);</cf> can be shortened to
-       <cf><m/P/.prepend(<m/A/);</cf> if <m/P/ is appropriate route attribute
-       (for example <cf/bgp_path/) or a local variable.
-       Similarly for <cf/delete/ and <cf/filter/.
+       Methods <cf>prepend</cf>, <cf>delete</cf> and <cf>filter</cf> keep the
+       original object intact as long as you use the result in any way. You can
+       also write e.g. <cf><m/P/.prepend(<m/A/);</cf> as a standalone statement.
+       This variant does modify the original object with the result of the operation.
 
        <tag><label id="type-bgpmask">bgpmask</tag>
        BGP masks are patterns used for BGP path matching (using <cf>path
@@ -1637,29 +1637,29 @@ in the foot).
 
        <cf><m/C/.len</cf> returns the length of clist <m/C/.
 
-       <cf><m/C/.empty</cf> makes the list <m/C/ empty.
+       <cf><m/C/.empty</cf> makes the list <m/C/ empty. Can't be used as a value, always modifies the object.
 
-       <cf>add(<m/C/,<m/P/)</cf> adds pair (or quad) <m/P/ to clist <m/C/ and
+       <cf><m/C/.add(<m/P/)</cf> adds pair (or quad) <m/P/ to clist <m/C/ and
        returns the result. If item <m/P/ is already in clist <m/C/, it does
        nothing. <m/P/ may also be a clist, in that case all its members are
        added; i.e., it works as clist union.
 
-       <cf>delete(<m/C/,<m/P/)</cf> deletes pair (or quad) <m/P/ from clist
+       <cf><m/C/.delete(<m/P/)</cf> deletes pair (or quad) <m/P/ from clist
        <m/C/ and returns the result. If clist <m/C/ does not contain item
        <m/P/, it does nothing. <m/P/ may also be a pair (or quad) set, in that
        case the operator deletes all items from clist <m/C/ that are also
        members of set <m/P/. Moreover, <m/P/ may also be a clist, which works
        analogously; i.e., it works as clist difference.
 
-       <cf>filter(<m/C/,<m/P/)</cf> deletes all items from clist <m/C/ that are
-       not members of pair (or quad) set <m/P/. I.e., <cf/filter/ do the same
+       <cf><m/C/.filter(<m/P/)</cf> deletes all items from clist <m/C/ that are
+       not members of pair (or quad) set <m/P/, and returns the result. I.e., <cf/filter/ do the same
        as <cf/delete/ with inverted set <m/P/. <m/P/ may also be a clist, which
        works analogously; i.e., it works as clist intersection.
 
-       Statement <cf><m/C/ = add(<m/C/, <m/P/);</cf> can be shortened to
-       <cf><m/C/.add(<m/P/);</cf> if <m/C/ is appropriate route attribute (for
-       example <cf/bgp_community/) or a local variable.
-       Similarly for <cf/delete/ and <cf/filter/.
+       Methods <cf>add</cf>, <cf>delete</cf> and <cf>filter</cf> keep the
+       original object intact as long as you use the result in any way. You can
+       also write e.g. <cf><m/P/.add(<m/A/);</cf> as a standalone statement.
+       This variant does modify the original object with the result of the operation.
 
        <cf><m/C/.min</cf> returns the minimum element of clist <m/C/.
 
index 6391569b309d534c3b7ad98505451b7fe904b5c1..c15814fd3905b8c7dd4c27cf6e7bb8757d0b273b 100644 (file)
@@ -23,21 +23,30 @@ static struct symbol *this_function;
 
 static struct f_method_scope {
   struct f_inst *object;
+  struct sym_scope *main;
   struct sym_scope scope;
 } f_method_scope_stack[32];
 static int f_method_scope_pos = -1;
 
 #define FM  (f_method_scope_stack[f_method_scope_pos])
 
-static inline void f_push_method_scope(struct f_inst *object)
+static inline void f_method_call_start(struct f_inst *object)
 {
+  if (object->type == T_VOID)
+    cf_error("Can't infer type to properly call a method, please assign the value to a variable");
   if (++f_method_scope_pos >= (int) ARRAY_SIZE(f_method_scope_stack))
     cf_error("Too many nested method calls");
+
+  struct sym_scope *scope = f_type_method_scope(object->type);
+  if (!scope)
+    cf_error("No methods defined for type %s", f_type_name(object->type));
+
   FM = (struct f_method_scope) {
     .object = object,
+    .main = new_config->current_scope,
     .scope = {
-      .next = new_config->current_scope,
-      .hash = global_root_scope[CFK_METHODS].hash,
+      .next = NULL,
+      .hash = scope->hash,
       .active = 1,
       .block = 1,
       .readonly = 1,
@@ -46,12 +55,23 @@ static inline void f_push_method_scope(struct f_inst *object)
   new_config->current_scope = &FM.scope;
 }
 
-static inline void f_pop_method_scope(void)
+static inline void f_method_call_args(void)
+{
+  ASSERT_DIE(FM.scope.active);
+  FM.scope.active = 0;
+
+  new_config->current_scope = FM.main;
+}
+
+static inline void f_method_call_end(void)
 {
   ASSERT_DIE(f_method_scope_pos >= 0);
+  if (FM.scope.active) {
+    ASSERT_DIE(&FM.scope == new_config->current_scope);
+    new_config->current_scope = FM.main;
 
-  ASSERT_DIE(&FM.scope == new_config->current_scope);
-  new_config->current_scope = FM.scope.next;
+    FM.scope.active = 0;
+  }
 
   f_method_scope_pos--;
 }
@@ -358,7 +378,7 @@ CF_METHODS(IS_V4, TYPE, IP, RD, LEN, MAXLEN, ASN, SRC, DST, MASK,
 %nonassoc ELSE
 
 %type <xp> cmds_int cmd_prep
-%type <x> term term_bs cmd cmd_var cmds cmds_scoped constant constructor print_list var var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail method_cmd method_term
+%type <x> term term_bs cmd cmd_var cmds cmds_scoped constant constructor print_list var var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail term_dot_method method_name_cont
 %type <fda> dynamic_attr
 %type <fsa> static_attr
 %type <f> filter where_filter
@@ -540,7 +560,7 @@ function_def:
    FUNCTION maybe_type symbol {
      DBG( "Beginning of function %s\n", $3->name );
      this_function = cf_define_symbol(new_config, $3, SYM_FUNCTION, function, NULL);
-/*   if ($2 == T_VOID) log(L_WARN "Support for functions without explicit return type will be removed soon" ); */
+/*   if ($2 == T_VOID) cf_warn("Support for functions without explicit return type will be removed soon" ); */
      cf_push_scope(new_config, this_function);
    } function_args {
      /* Make dummy f_line for storing function prototype */
@@ -850,33 +870,18 @@ static_attr:
  | ONLINK  { $$ = f_new_static_attr(T_BOOL,       SA_ONLINK,   0); }
  ;
 
-method_term:
-   IS_V4 { $$ = f_new_inst(FI_IS_V4, FM.object); }
- | TYPE { $$ = f_new_inst(FI_TYPE, FM.object); }
- | IP { $$ = f_new_inst(FI_IP, FM.object); }
- | RD { $$ = f_new_inst(FI_ROUTE_DISTINGUISHER, FM.object); }
- | LEN { $$ = f_new_inst(FI_LENGTH, FM.object); }
- | MAXLEN { $$ = f_new_inst(FI_ROA_MAXLEN, FM.object); }
- | ASN { $$ = f_new_inst(FI_ASN, FM.object); }
- | SRC { $$ = f_new_inst(FI_NET_SRC, FM.object); }
- | DST { $$ = f_new_inst(FI_NET_DST, FM.object); }
- | MASK '(' term ')' { $$ = f_new_inst(FI_IP_MASK, FM.object, $3); }
- | FIRST { $$ = f_new_inst(FI_AS_PATH_FIRST, FM.object); }
- | LAST  { $$ = f_new_inst(FI_AS_PATH_LAST, FM.object); }
- | LAST_NONAGGREGATED  { $$ = f_new_inst(FI_AS_PATH_LAST_NAG, FM.object); }
- | DATA { $$ = f_new_inst(FI_PAIR_DATA, FM.object); }
- | DATA1 { $$ = f_new_inst(FI_LC_DATA1, FM.object); }
- | DATA2 { $$ = f_new_inst(FI_LC_DATA2, FM.object); }
- | MIN  { $$ = f_new_inst(FI_MIN, FM.object); }
- | MAX  { $$ = f_new_inst(FI_MAX, FM.object); }
- ;
-
-method_cmd:
-   EMPTY               { $$ = f_const_empty(FM.object->i_FI_EA_GET.da.f_type); }
- | PREPEND '(' term ')'        { $$ = f_new_inst(FI_PATH_PREPEND, FM.object, $3 ); }
- | ADD '(' term ')'    { $$ = f_new_inst(FI_CLIST_ADD, FM.object, $3 ); }
- | DELETE '(' term ')' { $$ = f_new_inst(FI_CLIST_DEL, FM.object, $3 ); }
- | FILTER '(' term ')' { $$ = f_new_inst(FI_CLIST_FILTER, FM.object, $3 ); }
+term_dot_method: term '.' { f_method_call_start($1); } method_name_cont { $$ = $4; };
+method_name_cont:
+   CF_SYM_METHOD_BARE {
+     $$ = $1->method->new_inst(FM.object, NULL);
+     f_method_call_end();
+   }
+ | CF_SYM_METHOD_ARGS {
+     f_method_call_args();
+   } '(' var_list ')' {
+     $$ = $1->method->new_inst(FM.object, $4);
+     f_method_call_end();
+   }
  ;
 
 term:
@@ -906,21 +911,42 @@ term:
 
  | dynamic_attr { $$ = f_new_inst(FI_EA_GET, $1); }
 
- | term '.' {
-     f_push_method_scope($1);
-   } method_term {
-     f_pop_method_scope();
-     $$ = $4;
-   }
+ | term_dot_method
 
  | '+' EMPTY '+' { $$ = f_const_empty(T_PATH); }
  | '-' EMPTY '-' { $$ = f_const_empty(T_CLIST); }
  | '-' '-' EMPTY '-' '-' { $$ = f_const_empty(T_ECLIST); }
  | '-' '-' '-' EMPTY '-' '-' '-' { $$ = f_const_empty(T_LCLIST); }
  | PREPEND '(' term ',' term ')' { $$ = f_new_inst(FI_PATH_PREPEND, $3, $5); }
- | ADD '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_ADD, $3, $5); }
- | DELETE '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_DEL, $3, $5); }
- | FILTER '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_FILTER, $3, $5); }
+ | ADD '(' term ',' term ')' {
+     switch ($3->type) {
+       case T_CLIST: $$ = f_new_inst(FI_CLIST_ADD, $3, $5); break;
+       case T_ECLIST: $$ = f_new_inst(FI_ECLIST_ADD, $3, $5); break;
+       case T_LCLIST: $$ = f_new_inst(FI_LCLIST_ADD, $3, $5); break;
+       default: cf_error("Can't add to type %s", f_type_name($3->type));
+     }
+     cf_warn("add(x,y) function is deprecated, please use x.add(y)");
+   }
+ | DELETE '(' term ',' term ')' {
+     switch ($3->type) {
+       case T_PATH: $$ = f_new_inst(FI_PATH_DEL, $3, $5); break;
+       case T_CLIST: $$ = f_new_inst(FI_CLIST_DEL, $3, $5); break;
+       case T_ECLIST: $$ = f_new_inst(FI_ECLIST_DEL, $3, $5); break;
+       case T_LCLIST: $$ = f_new_inst(FI_LCLIST_DEL, $3, $5); break;
+       default: cf_error("Can't delete from type %s", f_type_name($3->type));
+     }
+     cf_warn("delete(x,y) function is deprecated, please use x.delete(y)");
+   }
+ | FILTER '(' term ',' term ')' {
+     switch ($3->type) {
+       case T_PATH: $$ = f_new_inst(FI_PATH_FILTER, $3, $5); break;
+       case T_CLIST: $$ = f_new_inst(FI_CLIST_FILTER, $3, $5); break;
+       case T_ECLIST: $$ = f_new_inst(FI_ECLIST_FILTER, $3, $5); break;
+       case T_LCLIST: $$ = f_new_inst(FI_LCLIST_FILTER, $3, $5); break;
+       default: cf_error("Can't filter type %s", f_type_name($3->type));
+     }
+     cf_warn("filter(x,y) function is deprecated, please use x.filter(y)");
+   }
 
  | ROA_CHECK '(' rtable ')' { $$ = f_new_inst(FI_ROA_CHECK_IMPLICIT, $3); }
  | ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_new_inst(FI_ROA_CHECK_EXPLICIT, $5, $7, $3); }
@@ -1008,7 +1034,7 @@ cmd:
      if (this_function->function->return_type == T_VOID)
      {
        if ($2->type != T_VOID)
-        log(L_WARN "Inferring function %s return type from its return value: %s", this_function->name, f_type_name($2->type));
+        cf_warn("Inferring function %s return type from its return value: %s", this_function->name, f_type_name($2->type));
        ((struct f_line *) this_function->function)->return_type = $2->type;
      }
      else if (this_function->function->return_type != $2->type)
@@ -1052,9 +1078,8 @@ cmd:
    }
 
  | lvalue '.' {
-     f_push_method_scope(f_lval_getter(&$1));
-   } method_cmd ';' {
-     f_pop_method_scope();
+     f_method_call_start(f_lval_getter(&$1));
+   } method_name_cont ';' {
      $$ = f_lval_setter(&$1, $4);
    }
  | BT_ASSERT '(' get_cf_position term get_cf_position ')' ';' { $$ = assert_done($4, $3 + 1, $5 - 1); }
index aa862f3f2d802847eeee5ec660928982ec1fd873..581150a2c51262e80055131b3a5ba141778d0311 100644 (file)
@@ -621,4 +621,3 @@ val_dump(const struct f_val *v) {
   val_format(v, &b);
   return val_dump_buffer;
 }
-
index f341b8b37f9d30ee979f5aa3590b79f43e5f1c89..baa7114c448f299009287c241c7b0e99c1a433fe 100644 (file)
@@ -64,6 +64,12 @@ enum f_type {
   T_PREFIX_SET = 0x81,
 } PACKED;
 
+struct f_method {
+  struct symbol *sym;
+  struct f_inst *(*new_inst)(struct f_inst *obj, struct f_inst *args);
+  uint arg_num;
+};
+
 /* Filter value; size of this affects filter memory consumption */
 struct f_val {
   enum f_type type;    /* T_*  */
@@ -277,8 +283,8 @@ trie_match_next_longest_ip6(net_addr_ip6 *n, ip6_addr *found)
 #define F_CMP_ERROR 999
 
 const char *f_type_name(enum f_type t);
-
 enum f_type f_type_element_type(enum f_type t);
+struct sym_scope *f_type_method_scope(enum f_type t);
 
 int val_same(const struct f_val *v1, const struct f_val *v2);
 int val_compare(const struct f_val *v1, const struct f_val *v2);
index 7c863bdc4db38978cdb42d4f7493e95c1e160805..46429f6aea90bec133e2b1fa46e434637baa06f5 100644 (file)
@@ -34,6 +34,9 @@ m4_divert(-1)m4_dnl
 #      102     constructor arguments
 #      110     constructor attributes
 #      103     constructor body
+#      111     method constructor body
+#      112     instruction constructor call from method constructor
+#      113     method constructor symbol registrator
 #      104     dump line item content
 #              (there may be nothing in dump-line content and
 #               it must be handled specially in phase 2)
@@ -48,6 +51,8 @@ m4_define(FID_STRUCT_IN, `m4_divert(101)')
 m4_define(FID_NEW_ARGS, `m4_divert(102)')
 m4_define(FID_NEW_ATTRIBUTES, `m4_divert(110)')
 m4_define(FID_NEW_BODY, `m4_divert(103)')
+m4_define(FID_NEW_METHOD, `m4_divert(111)')
+m4_define(FID_METHOD_CALL, `m4_divert(112)')
 m4_define(FID_DUMP_BODY, `m4_divert(104)m4_define([[FID_DUMP_BODY_EXISTS]])')
 m4_define(FID_LINEARIZE_BODY, `m4_divert(105)')
 m4_define(FID_SAME_BODY, `m4_divert(106)')
@@ -120,7 +125,13 @@ FID_IFCONST([[
     constargs = 0;
 ]])
 } while (child$1 = child$1->next);
-FID_LINEARIZE_BODY
+m4_define([[INST_METHOD_NUM_ARGS]],m4_eval($1-1))m4_dnl
+m4_ifelse($1,1,,[[FID_NEW_METHOD()m4_dnl
+  struct f_inst *arg$1 = args;
+  if (args == NULL) cf_error("Not enough arguments"); /* INST_NAME */
+  args = args->next;
+  FID_METHOD_CALL()    , arg$1]])
+FID_LINEARIZE_BODY()m4_dnl
 pos = linearize(dest, whati->f$1, pos);
 FID_INTERPRET_BODY()')
 
@@ -170,28 +181,29 @@ FID_HIC(,[[
 m4_define(ARG, `ARG_ANY($1) ARG_TYPE($1,$2)')
 m4_define(ARG_TYPE, `ARG_TYPE_STATIC($1,$2) ARG_TYPE_DYNAMIC($1,$2)')
 
-m4_define(ARG_TYPE_STATIC, `
+m4_define(ARG_TYPE_STATIC, `m4_dnl
+m4_ifelse($1,1,[[m4_define([[INST_METHOD_OBJECT_TYPE]],$2)]],)m4_dnl
 FID_NEW_BODY()m4_dnl
 if (f$1->type && (f$1->type != ($2)) && !f_const_promotion(f$1, ($2)))
   cf_error("Argument $1 of %s must be of type %s, got type %s",
           f_instruction_name(what->fi_code), f_type_name($2), f_type_name(f$1->type));
 FID_INTERPRET_BODY()')
 
-m4_define(ARG_TYPE_DYNAMIC, `
+m4_define(ARG_TYPE_DYNAMIC, `m4_dnl
 FID_INTERPRET_EXEC()m4_dnl
 if (v$1.type != ($2))
   runtime("Argument $1 of %s must be of type %s, got type %s",
           f_instruction_name(what->fi_code), f_type_name($2), f_type_name(v$1.type));
 FID_INTERPRET_BODY()')
 
-m4_define(ARG_SAME_TYPE, `
+m4_define(ARG_SAME_TYPE, `m4_dnl
 FID_NEW_BODY()m4_dnl
 if (f$1->type && f$2->type && (f$1->type != f$2->type) &&
    !f_const_promotion(f$2, f$1->type) && !f_const_promotion(f$1, f$2->type))
   cf_error("Arguments $1 and $2 of %s must be of the same type", f_instruction_name(what->fi_code));
 FID_INTERPRET_BODY()')
 
-m4_define(ARG_PREFER_SAME_TYPE, `
+m4_define(ARG_PREFER_SAME_TYPE, `m4_dnl
 FID_NEW_BODY()m4_dnl
 if (f$1->type && f$2->type && (f$1->type != f$2->type))
    (void) (f_const_promotion(f$2, f$1->type) || f_const_promotion(f$1, f$2->type));
@@ -266,6 +278,14 @@ m4_define(STATIC_ATTR, `FID_MEMBER(struct f_static_attr, sa, f1->sa.sa_code != f
 m4_define(DYNAMIC_ATTR, `FID_MEMBER(struct f_dynamic_attr, da, f1->da.ea_code != f2->da.ea_code,,)')
 m4_define(ACCESS_RTE, `FID_HIC(,[[do { if (!fs->rte) runtime("No route to access"); } while (0)]],NEVER_CONSTANT())')
 
+#      Method constructor block
+m4_define(METHOD_CONSTRUCTOR, `m4_dnl
+FID_NEW_METHOD()m4_dnl
+    if (args) cf_error("Too many arguments");
+m4_define([[INST_IS_METHOD]])
+m4_define([[INST_METHOD_NAME]],$1)
+FID_INTERPRET_BODY()')
+
 #      2) Code wrapping
 #      The code produced in 1xx temporary diversions is a raw code without
 #      any auxiliary commands and syntactical structures around. When the
@@ -285,6 +305,7 @@ m4_define(ACCESS_RTE, `FID_HIC(,[[do { if (!fs->rte) runtime("No route to access
 #      10      iterate
 #      1       union in struct f_inst
 #      3       constructors + interpreter
+#      11      method constructors
 #
 #      These global diversions contain blocks of code that can be directly
 #      put into the final file, yet it still can't be written out now as
@@ -304,6 +325,9 @@ m4_define(FID_DUMP_CALLER, `FID_ZONE(7, Dump line caller)')
 m4_define(FID_LINEARIZE, `FID_ZONE(8, Linearize)')
 m4_define(FID_SAME, `FID_ZONE(9, Comparison)')
 m4_define(FID_ITERATE, `FID_ZONE(10, Iteration)')
+m4_define(FID_METHOD, `FID_ZONE(11, Method constructor)')
+m4_define(FID_METHOD_SCOPE_INIT, `FID_ZONE(12, Method scope initializator)')
+m4_define(FID_METHOD_REGISTER, `FID_ZONE(13, Method registrator)')
 
 #      This macro does all the code wrapping. See inline comments.
 m4_define(INST_FLUSH, `m4_ifdef([[INST_NAME]], [[
@@ -361,6 +385,35 @@ m4_undivert(102)m4_dnl
   }
 ]])
 
+m4_ifdef([[INST_IS_METHOD]],m4_dnl
+FID_METHOD()m4_dnl
+[[struct f_inst * NONNULL(1)
+f_new_method_]]INST_NAME()[[(struct f_inst *obj, struct f_inst *args)
+  {
+    /* Unwind the arguments (INST_METHOD_NUM_ARGS) */
+    m4_undivert(111)m4_dnl
+    return f_new_inst(INST_NAME, obj
+m4_undivert(112)
+    );
+  }
+
+FID_METHOD_SCOPE_INIT()m4_dnl
+  [INST_METHOD_OBJECT_TYPE] = {},
+FID_METHOD_REGISTER()m4_dnl
+  sym = cf_new_symbol(&f_type_method_scopes[INST_METHOD_OBJECT_TYPE],
+                     global_root_scope_pool, global_root_scope_linpool,
+                     INST_METHOD_NAME);
+  sym->class = SYM_METHOD;
+  sym->method = method = lp_allocz(global_root_scope_linpool, sizeof(struct f_method));
+
+  *method = (struct f_method) {
+    .sym = sym,
+    .new_inst = f_new_method_]]INST_NAME()[[,
+    .arg_num = INST_METHOD_NUM_ARGS,
+  };
+
+]])m4_dnl
+
 FID_DUMP_CALLER()m4_dnl                         Case in another big switch used in instruction dumping (debug)
 case INST_NAME(): f_dump_line_item_]]INST_NAME()[[(item, indent + 1); break;
 
@@ -414,6 +467,8 @@ m4_define([[INST_INVAL]], [[$2]])m4_dnl             instruction input value count,
 m4_define([[INST_OUTVAL]], [[$3]])m4_dnl       instruction output value count,
 m4_undefine([[INST_NEVER_CONSTANT]])m4_dnl     reset NEVER_CONSTANT trigger,
 m4_undefine([[INST_RESULT_TYPE]])m4_dnl                and reset RESULT_TYPE value.
+m4_undefine([[INST_IS_METHOD]])m4_dnl          and reset method constructor request.
+m4_undefine([[INST_METHOD_OBJECT_TYPE]],)m4_dnl        reset method object type,
 FID_INTERPRET_BODY()m4_dnl                     By default, every code is interpreter code.
 ')
 
@@ -537,6 +592,37 @@ FID_WR_PUT(3)
 #undef v3
 #undef vv
 
+/* Method constructor wrappers */
+FID_WR_PUT(11)
+
+#if defined(__GNUC__) && __GNUC__ >= 6
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Woverride-init"
+#endif
+
+static struct sym_scope f_type_method_scopes[] = {
+FID_WR_PUT(12)
+};
+
+#if defined(__GNUC__) && __GNUC__ >= 6
+#pragma GCC diagnostic pop
+#endif
+
+struct sym_scope *f_type_method_scope(enum f_type t)
+{
+  return (t < ARRAY_SIZE(f_type_method_scopes)) ? &f_type_method_scopes[t] : NULL;
+}
+
+void f_type_methods_register(void)
+{
+  struct symbol *sym;
+  struct f_method *method;
+FID_WR_PUT(13)
+
+  for (uint i = 0; i < ARRAY_SIZE(f_type_method_scopes); i++)
+    f_type_method_scopes[i].readonly = 1;
+}
+
 /* Line dumpers */
 #define INDENT (((const char *) f_dump_line_indent_str) + sizeof(f_dump_line_indent_str) - (indent) - 1)
 static const char f_dump_line_indent_str[] = "                                ";
index 9ecd8ffb5ca6caabb755095b7c8a08e13c7f7776..16d01e4d200189566af4b7d984389b2a35427268 100644 (file)
@@ -72,6 +72,8 @@
  *     m4_dnl    ACCESS_RTE;                           this instruction needs route
  *     m4_dnl    ACCESS_EATTRS;                        this instruction needs extended attributes
  *
+ *     m4_dnl    METHOD_CONSTRUCTOR(name);             this instruction is in fact a method of the first argument's type; register it with the given name for that type
+ *
  *     m4_dnl    FID_MEMBER(                           custom instruction member
  *     m4_dnl      C type,                             for storage in structs
  *     m4_dnl      name,                               how the member is named
     RESULT(T_BOOL, i, (v1.type != T_VOID) && !undef_value(v1));
   }
 
-  INST(FI_TYPE, 1, 1) {
-    ARG_ANY(1); /* There may be more types supporting this operation */
-    switch (v1.type)
-    {
-      case T_NET:
-       RESULT(T_ENUM_NETTYPE, i, v1.val.net->type);
-       break;
-      default:
-       runtime( "Can't determine type of this item" );
-    }
+  INST(FI_NET_TYPE, 1, 1) {
+    ARG(1, T_NET);
+    METHOD_CONSTRUCTOR("type");
+    RESULT(T_ENUM_NETTYPE, i, v1.val.net->type);
   }
 
   INST(FI_IS_V4, 1, 1) {
     ARG(1, T_IP);
+    METHOD_CONSTRUCTOR("is_v4");
     RESULT(T_BOOL, i, ipa_is_ip4(v1.val.ip));
   }
 
     RESULT_VAL(val);
   }
 
+  INST(FI_PATH_EMPTY, 1, 1) {
+    ARG(1, T_PATH);
+    METHOD_CONSTRUCTOR("empty");
+    RESULT(T_PATH, ad, &null_adata);
+  }
+
+  INST(FI_CLIST_EMPTY, 1, 1) {
+    ARG(1, T_CLIST);
+    METHOD_CONSTRUCTOR("empty");
+    RESULT(T_CLIST, ad, &null_adata);
+  }
+
+  INST(FI_ECLIST_EMPTY, 1, 1) {
+    ARG(1, T_ECLIST);
+    METHOD_CONSTRUCTOR("empty");
+    RESULT(T_ECLIST, ad, &null_adata);
+  }
+
+  INST(FI_LCLIST_EMPTY, 1, 1) {
+    ARG(1, T_LCLIST);
+    METHOD_CONSTRUCTOR("empty");
+    RESULT(T_LCLIST, ad, &null_adata);
+  }
+
   INST(FI_FOR_INIT, 1, 0) {
     NEVER_CONSTANT;
     ARG_ANY(1);
     ea_unset_attr(fs->eattrs, fs->pool, 1, da.ea_code);
   }
 
-  INST(FI_LENGTH, 1, 1) {      /* Get length of */
-    ARG_ANY(1);
-    switch(v1.type) {
-    case T_NET:    RESULT(T_INT, i, net_pxlen(v1.val.net)); break;
-    case T_PATH:   RESULT(T_INT, i, as_path_getlen(v1.val.ad)); break;
-    case T_CLIST:  RESULT(T_INT, i, int_set_get_size(v1.val.ad)); break;
-    case T_ECLIST: RESULT(T_INT, i, ec_set_get_size(v1.val.ad)); break;
-    case T_LCLIST: RESULT(T_INT, i, lc_set_get_size(v1.val.ad)); break;
-    default: runtime( "Prefix, path, clist or eclist expected" );
-    }
+  INST(FI_NET_LENGTH, 1, 1) {  /* Get length of */
+    ARG(1, T_NET);
+    METHOD_CONSTRUCTOR("len");
+    RESULT(T_INT, i, net_pxlen(v1.val.net));
+  }
+
+  INST(FI_PATH_LENGTH, 1, 1) { /* Get length of */
+    ARG(1, T_PATH);
+    METHOD_CONSTRUCTOR("len");
+    RESULT(T_INT, i, as_path_getlen(v1.val.ad));
+  }
+
+  INST(FI_CLIST_LENGTH, 1, 1) {        /* Get length of */
+    ARG(1, T_CLIST);
+    METHOD_CONSTRUCTOR("len");
+    RESULT(T_INT, i, int_set_get_size(v1.val.ad));
+  }
+
+  INST(FI_ECLIST_LENGTH, 1, 1) { /* Get length of */
+    ARG(1, T_ECLIST);
+    METHOD_CONSTRUCTOR("len");
+    RESULT(T_INT, i, ec_set_get_size(v1.val.ad));
+  }
+
+  INST(FI_LCLIST_LENGTH, 1, 1) {       /* Get length of */
+    ARG(1, T_LCLIST);
+    METHOD_CONSTRUCTOR("len");
+    RESULT(T_INT, i, lc_set_get_size(v1.val.ad));
   }
 
   INST(FI_NET_SRC, 1, 1) {     /* Get src prefix */
     ARG(1, T_NET);
+    METHOD_CONSTRUCTOR("src");
 
     net_addr_union *net = (void *) v1.val.net;
     net_addr *src = falloc(sizeof(net_addr_ip6));
 
   INST(FI_NET_DST, 1, 1) {     /* Get dst prefix */
     ARG(1, T_NET);
+    METHOD_CONSTRUCTOR("dst");
 
     net_addr_union *net = (void *) v1.val.net;
     net_addr *dst = falloc(sizeof(net_addr_ip6));
 
   INST(FI_ROA_MAXLEN, 1, 1) {  /* Get ROA max prefix length */
     ARG(1, T_NET);
+    METHOD_CONSTRUCTOR("maxlen")
     if (!net_is_roa(v1.val.net))
       runtime( "ROA expected" );
 
       ((net_addr_roa6 *) v1.val.net)->max_pxlen);
   }
 
-  INST(FI_ASN, 1, 1) {         /* Get ROA ASN or community ASN part */
-    ARG_ANY(1);
-    RESULT_TYPE(T_INT);
-    switch(v1.type)
-    {
-      case T_NET:
+  INST(FI_NET_ASN, 1, 1) {     /* Get ROA ASN or community ASN part */
+    ARG(1, T_NET);
+    METHOD_CONSTRUCTOR("asn");
         if (!net_is_roa(v1.val.net))
           runtime( "ROA expected" );
 
-        RESULT_(T_INT, i, (v1.val.net->type == NET_ROA4) ?
+        RESULT(T_INT, i, (v1.val.net->type == NET_ROA4) ?
           ((net_addr_roa4 *) v1.val.net)->asn :
           ((net_addr_roa6 *) v1.val.net)->asn);
-        break;
-
-      case T_PAIR:
-        RESULT_(T_INT, i, v1.val.i >> 16);
-        break;
+  }
 
-      case T_LC:
-        RESULT_(T_INT, i, v1.val.lc.asn);
-        break;
+  INST(FI_PAIR_ASN, 1, 1) {    /* Get ROA ASN or community ASN part */
+    ARG(1, T_PAIR);
+    METHOD_CONSTRUCTOR("asn");
+    RESULT(T_INT, i, v1.val.i >> 16);
+  }
 
-      default:
-        runtime( "Net, pair or lc expected" );
-    }
+  INST(FI_LC_ASN, 1, 1) {      /* Get ROA ASN or community ASN part */
+    ARG(1, T_LC);
+    METHOD_CONSTRUCTOR("asn");
+    RESULT(T_INT, i, v1.val.lc.asn);
   }
 
-  INST(FI_IP, 1, 1) {  /* Convert prefix to ... */
+  INST(FI_NET_IP, 1, 1) {      /* Convert prefix to ... */
     ARG(1, T_NET);
+    METHOD_CONSTRUCTOR("ip");
     RESULT(T_IP, ip, net_prefix(v1.val.net));
   }
 
   INST(FI_ROUTE_DISTINGUISHER, 1, 1) {
     ARG(1, T_NET);
+    METHOD_CONSTRUCTOR("rd");
     if (!net_is_vpn(v1.val.net))
       runtime( "VPN address expected" );
     RESULT(T_RD, ec, net_rd(v1.val.net));
 
   INST(FI_AS_PATH_FIRST, 1, 1) {       /* Get first ASN from AS PATH */
     ARG(1, T_PATH);
+    METHOD_CONSTRUCTOR("first");
     u32 as = 0;
     as_path_get_first(v1.val.ad, &as);
     RESULT(T_INT, i, as);
 
   INST(FI_AS_PATH_LAST, 1, 1) {                /* Get last ASN from AS PATH */
     ARG(1, T_PATH);
+    METHOD_CONSTRUCTOR("last");
     u32 as = 0;
     as_path_get_last(v1.val.ad, &as);
     RESULT(T_INT, i, as);
 
   INST(FI_AS_PATH_LAST_NAG, 1, 1) {    /* Get last ASN from non-aggregated part of AS PATH */
     ARG(1, T_PATH);
+    METHOD_CONSTRUCTOR("last_nonaggregated");
     RESULT(T_INT, i, as_path_get_last_nonaggregated(v1.val.ad));
   }
 
   INST(FI_PAIR_DATA, 1, 1) {   /* Get data part from the standard community */
     ARG(1, T_PAIR);
+    METHOD_CONSTRUCTOR("data");
     RESULT(T_INT, i, v1.val.i & 0xFFFF);
   }
 
   INST(FI_LC_DATA1, 1, 1) {    /* Get data1 part from the large community */
     ARG(1, T_LC);
+    METHOD_CONSTRUCTOR("data1");
     RESULT(T_INT, i, v1.val.lc.ldp1);
   }
 
   INST(FI_LC_DATA2, 1, 1) {    /* Get data2 part from the large community */
     ARG(1, T_LC);
+    METHOD_CONSTRUCTOR("data2");
     RESULT(T_INT, i, v1.val.lc.ldp2);
   }
 
-  INST(FI_MIN, 1, 1) { /* Get minimum element from list */
-    ARG_ANY(1);
-    RESULT_TYPE(f_type_element_type(v1.type));
-    switch(v1.type)
-    {
-      case T_CLIST:
-        {
-          u32 val = 0;
-          int_set_min(v1.val.ad, &val);
-          RESULT_(T_PAIR, i, val);
-        }
-        break;
-
-      case T_ECLIST:
-        {
-          u64 val = 0;
-          ec_set_min(v1.val.ad, &val);
-          RESULT_(T_EC, ec, val);
-        }
-        break;
-
-      case T_LCLIST:
-        {
-          lcomm val = { 0, 0, 0 };
-          lc_set_min(v1.val.ad, &val);
-          RESULT_(T_LC, lc, val);
-        }
-        break;
+  INST(FI_CLIST_MIN, 1, 1) {   /* Get minimum element from list */
+    ARG(1, T_CLIST);
+    METHOD_CONSTRUCTOR("min");
+    u32 val = 0;
+    int_set_min(v1.val.ad, &val);
+    RESULT(T_PAIR, i, val);
+  }
 
-      default:
-        runtime( "Clist or lclist expected" );
-    }
+  INST(FI_CLIST_MAX, 1, 1) {   /* Get minimum element from list */
+    ARG(1, T_CLIST);
+    METHOD_CONSTRUCTOR("max");
+    u32 val = 0;
+    int_set_max(v1.val.ad, &val);
+    RESULT(T_PAIR, i, val);
   }
 
-  INST(FI_MAX, 1, 1) { /* Get maximum element from list */
-    ARG_ANY(1);
-    RESULT_TYPE(f_type_element_type(v1.type));
-    switch(v1.type)
-    {
-      case T_CLIST:
-        {
-          u32 val = 0;
-          int_set_max(v1.val.ad, &val);
-          RESULT_(T_PAIR, i, val);
-        }
-        break;
+  INST(FI_ECLIST_MIN, 1, 1) {  /* Get minimum element from list */
+    ARG(1, T_ECLIST);
+    METHOD_CONSTRUCTOR("min");
+    u64 val = 0;
+    ec_set_min(v1.val.ad, &val);
+    RESULT(T_EC, ec, val);
+  }
 
-      case T_ECLIST:
-        {
-          u64 val = 0;
-          ec_set_max(v1.val.ad, &val);
-          RESULT_(T_EC, ec, val);
-        }
-        break;
+  INST(FI_ECLIST_MAX, 1, 1) {  /* Get minimum element from list */
+    ARG(1, T_ECLIST);
+    METHOD_CONSTRUCTOR("max");
+    u64 val = 0;
+    ec_set_max(v1.val.ad, &val);
+    RESULT(T_EC, ec, val);
+  }
 
-      case T_LCLIST:
-        {
-          lcomm val = { 0, 0, 0 };
-          lc_set_max(v1.val.ad, &val);
-          RESULT_(T_LC, lc, val);
-        }
-        break;
+  INST(FI_LCLIST_MIN, 1, 1) {  /* Get minimum element from list */
+    ARG(1, T_LCLIST);
+    METHOD_CONSTRUCTOR("min");
+    lcomm val = {};
+    lc_set_min(v1.val.ad, &val);
+    RESULT(T_LC, lc, val);
+  }
 
-      default:
-        runtime( "Clist or lclist expected" );
-    }
+  INST(FI_LCLIST_MAX, 1, 1) {  /* Get minimum element from list */
+    ARG(1, T_LCLIST);
+    METHOD_CONSTRUCTOR("max");
+    lcomm val = {};
+    lc_set_max(v1.val.ad, &val);
+    RESULT(T_LC, lc, val);
   }
 
   INST(FI_RETURN, 1, 0) {
   INST(FI_IP_MASK, 2, 1) { /* IP.MASK(val) */
     ARG(1, T_IP);
     ARG(2, T_INT);
+    METHOD_CONSTRUCTOR("mask");
     RESULT(T_IP, ip, [[ ipa_is_ip4(v1.val.ip) ?
       ipa_from_ip4(ip4_and(ipa_to_ip4(v1.val.ip), ip4_mkmask(v2.val.i))) :
       ipa_from_ip6(ip6_and(ipa_to_ip6(v1.val.ip), ip6_mkmask(v2.val.i))) ]]);
   INST(FI_PATH_PREPEND, 2, 1) {        /* Path prepend */
     ARG(1, T_PATH);
     ARG(2, T_INT);
+    METHOD_CONSTRUCTOR("prepend");
     RESULT(T_PATH, ad, [[ as_path_prepend(fpool, v1.val.ad, v2.val.i) ]]);
   }
 
   INST(FI_CLIST_ADD, 2, 1) {   /* (Extended) Community list add */
-    ARG_ANY(1);
+    ARG(1, T_CLIST);
     ARG_ANY(2);
-    RESULT_TYPE(f1->type);
-
-    if (v1.type == T_PATH)
-      runtime("Can't add to path");
-
-    else if (v1.type == T_CLIST)
-    {
-      /* Community (or cluster) list */
+    METHOD_CONSTRUCTOR("add");
       struct f_val dummy;
-
       if ((v2.type == T_PAIR) || (v2.type == T_QUAD))
-       RESULT_(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
+       RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
       /* IP->Quad implicit conversion */
       else if (val_is_ip4(&v2))
-       RESULT_(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
+       RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
       else if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy))
        runtime("Can't add set");
       else if (v2.type == T_CLIST)
-       RESULT_(T_CLIST, ad, [[ int_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
+       RESULT(T_CLIST, ad, [[ int_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
       else
        runtime("Can't add non-pair");
-    }
+  }
 
-    else if (v1.type == T_ECLIST)
-    {
+  INST(FI_ECLIST_ADD, 2, 1) {
+    ARG(1, T_ECLIST);
+    ARG_ANY(2);
+    METHOD_CONSTRUCTOR("add");
       /* v2.val is either EC or EC-set */
       if ((v2.type == T_SET) && eclist_set_type(v2.val.t))
        runtime("Can't add set");
       else if (v2.type == T_ECLIST)
-       RESULT_(T_ECLIST, ad, [[ ec_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
+       RESULT(T_ECLIST, ad, [[ ec_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
       else if (v2.type != T_EC)
        runtime("Can't add non-ec");
       else
-       RESULT_(T_ECLIST, ad, [[ ec_set_add(fpool, v1.val.ad, v2.val.ec) ]]);
-    }
+       RESULT(T_ECLIST, ad, [[ ec_set_add(fpool, v1.val.ad, v2.val.ec) ]]);
+  }
 
-    else if (v1.type == T_LCLIST)
-    {
+  INST(FI_LCLIST_ADD, 2, 1) {
+    ARG(1, T_LCLIST);
+    ARG_ANY(2);
+    METHOD_CONSTRUCTOR("add");
       /* v2.val is either LC or LC-set */
       if ((v2.type == T_SET) && lclist_set_type(v2.val.t))
        runtime("Can't add set");
       else if (v2.type == T_LCLIST)
-       RESULT_(T_LCLIST, ad, [[ lc_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
+       RESULT(T_LCLIST, ad, [[ lc_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
       else if (v2.type != T_LC)
        runtime("Can't add non-lc");
       else
-       RESULT_(T_LCLIST, ad, [[ lc_set_add(fpool, v1.val.ad, v2.val.lc) ]]);
-
-    }
-
-    else
-      runtime("Can't add to non-[e|l]clist");
+       RESULT(T_LCLIST, ad, [[ lc_set_add(fpool, v1.val.ad, v2.val.lc) ]]);
   }
 
-  INST(FI_CLIST_DEL, 2, 1) {   /* (Extended) Community list add or delete */
-    ARG_ANY(1);
+  INST(FI_PATH_DEL, 2, 1) {    /* Path delete */
+    ARG(1, T_PATH);
     ARG_ANY(2);
-    RESULT_TYPE(f1->type);
-
-    if (v1.type == T_PATH)
-    {
+    METHOD_CONSTRUCTOR("delete");
       if ((v2.type == T_SET) && path_set_type(v2.val.t) || (v2.type == T_INT))
-       RESULT_(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 0) ]]);
+       RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 0) ]]);
       else
        runtime("Can't delete non-integer (set)");
-    }
+  }
 
-    else if (v1.type == T_CLIST)
-    {
+  INST(FI_CLIST_DEL, 2, 1) {   /* (Extended) Community list add or delete */
+    ARG(1, T_CLIST);
+    ARG_ANY(2);
+    METHOD_CONSTRUCTOR("delete");
       /* Community (or cluster) list */
       struct f_val dummy;
 
       if ((v2.type == T_PAIR) || (v2.type == T_QUAD))
-       RESULT_(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
+       RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
       /* IP->Quad implicit conversion */
       else if (val_is_ip4(&v2))
-       RESULT_(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
+       RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
       else if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy) || (v2.type == T_CLIST))
-       RESULT_(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+       RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
       else
        runtime("Can't delete non-pair");
-    }
+  }
 
-    else if (v1.type == T_ECLIST)
-    {
+  INST(FI_ECLIST_DEL, 2, 1) {  /* (Extended) Community list add or delete */
+    ARG(1, T_ECLIST);
+    ARG_ANY(2);
+    METHOD_CONSTRUCTOR("delete");
       /* v2.val is either EC or EC-set */
       if ((v2.type == T_SET) && eclist_set_type(v2.val.t) || (v2.type == T_ECLIST))
-       RESULT_(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+       RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
       else if (v2.type != T_EC)
        runtime("Can't delete non-ec");
       else
-       RESULT_(T_ECLIST, ad, [[ ec_set_del(fpool, v1.val.ad, v2.val.ec) ]]);
-    }
+       RESULT(T_ECLIST, ad, [[ ec_set_del(fpool, v1.val.ad, v2.val.ec) ]]);
+  }
 
-    else if (v1.type == T_LCLIST)
-    {
+  INST(FI_LCLIST_DEL, 2, 1) {  /* (Extended) Community list add or delete */
+    ARG(1, T_LCLIST);
+    ARG_ANY(2);
+    METHOD_CONSTRUCTOR("delete");
       /* v2.val is either LC or LC-set */
       if ((v2.type == T_SET) && lclist_set_type(v2.val.t) || (v2.type == T_LCLIST))
-       RESULT_(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+       RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
       else if (v2.type != T_LC)
        runtime("Can't delete non-lc");
       else
-       RESULT_(T_LCLIST, ad, [[ lc_set_del(fpool, v1.val.ad, v2.val.lc) ]]);
-    }
-
-    else
-      runtime("Can't delete in non-[e|l]clist");
+       RESULT(T_LCLIST, ad, [[ lc_set_del(fpool, v1.val.ad, v2.val.lc) ]]);
   }
 
-  INST(FI_CLIST_FILTER, 2, 1) {        /* (Extended) Community list add or delete */
-    ARG_ANY(1);
+  INST(FI_PATH_FILTER, 2, 1) { /* (Extended) Community list add or delete */
+    ARG(1, T_PATH);
     ARG_ANY(2);
-    RESULT_TYPE(f1->type);
+    METHOD_CONSTRUCTOR("filter");
 
-    if (v1.type == T_PATH)
-    {
       if ((v2.type == T_SET) && path_set_type(v2.val.t))
-       RESULT_(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 1) ]]);
       else
        runtime("Can't filter integer");
     }
 
-    else if (v1.type == T_CLIST)
-    {
+  INST(FI_CLIST_FILTER, 2, 1) {
+    ARG(1, T_CLIST);
+    ARG_ANY(2);
+    METHOD_CONSTRUCTOR("filter");
       /* Community (or cluster) list */
       struct f_val dummy;
 
       if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy) || (v2.type == T_CLIST))
-       RESULT_(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
       else
        runtime("Can't filter pair");
-    }
+  }
 
-    else if (v1.type == T_ECLIST)
-    {
+  INST(FI_ECLIST_FILTER, 2, 1) {
+    ARG(1, T_ECLIST);
+    ARG_ANY(2);
+    METHOD_CONSTRUCTOR("filter");
       /* v2.val is either EC or EC-set */
       if ((v2.type == T_SET) && eclist_set_type(v2.val.t) || (v2.type == T_ECLIST))
-       RESULT_(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
       else
        runtime("Can't filter ec");
-    }
+  }
 
-    else if (v1.type == T_LCLIST)
-    {
+  INST(FI_LCLIST_FILTER, 2, 1) {
+    ARG(1, T_LCLIST);
+    ARG_ANY(2);
+    METHOD_CONSTRUCTOR("filter");
       /* v2.val is either LC or LC-set */
       if ((v2.type == T_SET) && lclist_set_type(v2.val.t) || (v2.type == T_LCLIST))
-       RESULT_(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
       else
        runtime("Can't filter lc");
-    }
-
-    else
-      runtime("Can't filter non-[e|l]clist");
   }
 
   INST(FI_ROA_CHECK_IMPLICIT, 0, 1) {  /* ROA Check */
index 33389a17faad53e154b1d1c776e80ea93b3fdac0..90f5e694957554579f33a9c1c5ccea58889b6dab 100644 (file)
@@ -756,7 +756,7 @@ function bgpmask mkpath(int a; int b)
 
 define set35 = [3 .. 5];
 
-function t_path()
+function t_path_old()
 bgpmask pm1;
 bgppath p2;
 int set set12;
@@ -835,7 +835,91 @@ int set set12;
        bt_assert(x = 18 && y = 50);
 }
 
-bt_test_suite(t_path, "Testing paths");
+bt_test_suite(t_path_old, "Testing paths (old syntax)");
+
+
+function t_path_new()
+{
+       bgpmask pm1 = [= 4 3 2 1 =];
+       int set set12 = [1, 2];
+
+       bt_assert(format(pm1) = "[= 4 3 2 1 =]");
+
+       bt_assert(+empty+ = +empty+);
+       bt_assert(10 !~ +empty+);
+
+       bgppath p2;
+       bt_assert(p2 = +empty+);
+       p2.prepend(1);
+       p2.prepend(2);
+       p2.prepend(3);
+       p2.prepend(4);
+
+       bt_assert(p2.empty = +empty+);
+
+       bt_assert(format(p2) = "(path 4 3 2 1)");
+       bt_assert(p2.len = 4);
+       bt_assert(p2 ~ pm1);
+       bt_assert(3 ~ p2);
+       bt_assert(p2 ~ [2, 10..20]);
+       bt_assert(p2 ~ [4, 10..20]);
+       bt_assert(p2 !~ []);
+
+       p2.prepend(5);
+       bt_assert(p2 !~ pm1);
+       bt_assert(10 !~ p2);
+       bt_assert(p2 !~ [8, ten..(2*ten)]);
+       bt_assert(p2 ~ [= * 4 3 * 1 =]);
+       bt_assert(p2 ~ [= (3+2) (2*2) 3 2 1 =]);
+       bt_assert(p2 ~ [= 5 [2, 4, 6] 3 [1..2] 1 =]);
+       bt_assert(p2 ~ [= 5 set35 3 set12 set12 =]);
+       bt_assert(p2 ~ mkpath(5, 4));
+       bt_assert(p2 ~ [= * [3] * =]);
+       bt_assert(p2 !~ [= * [] * =]);
+
+       bt_assert(p2.len = 5);
+       bt_assert(p2.first = 5);
+       bt_assert(p2.last = 1);
+
+       bt_assert(p2.len = 5);
+       bt_assert(p2.delete(3) = +empty+.prepend(1).prepend(2).prepend(4).prepend(5));
+       bt_assert(p2.filter([1..3]) = +empty+.prepend(1).prepend(2).prepend(3));
+       bt_assert(p2.delete([]) = p2);
+       bt_assert(p2.filter([]) = +empty+);
+       bt_assert(+empty+.prepend(0).prepend(1).delete([]) = +empty+.prepend(0).prepend(1));
+       bt_assert(+empty+.prepend(0).prepend(1).filter([]) = +empty+);
+
+       p2 = +empty+;
+       p2.prepend(5);
+       p2.prepend(4);
+       p2.prepend(3);
+       p2.prepend(3);
+       p2.prepend(2);
+       p2.prepend(1);
+
+       bt_assert(p2 !~ [= 1 2 3 4 5 =]);
+       bt_assert(p2 ~ [= 1 2 * 4 5 =]);
+       bt_assert(p2 ~ [= 1 2 * 3 4 5 =]);
+       bt_assert(p2 ~ [= 1 2 3+ 4 5 =]);
+       bt_assert(p2 ~ [= 1 2 3+ 4+ 5 =]);
+       bt_assert(p2 !~ [= 1 2 3+ 5+ 4 5 =]);
+       bt_assert(p2 !~ [= 1 2 3 3 5+ 4 5 =]);
+       bt_assert(p2.delete(3) = +empty+.prepend(5).prepend(4).prepend(2).prepend(1));
+       bt_assert(p2.delete([4..5]) = +empty+.prepend(3).prepend(3).prepend(2).prepend(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_new, "Testing paths (new syntax)");
 
 
 
@@ -847,7 +931,7 @@ bt_test_suite(t_path, "Testing paths");
 
 define p23 = (2, 3);
 
-function t_clist()
+function t_clist_old()
 clist l;
 clist l2;
 clist r;
@@ -967,7 +1051,129 @@ clist r;
        bt_assert(x = 36);
 }
 
-bt_test_suite(t_clist, "Testing lists of communities");
+bt_test_suite(t_clist_old, "Testing lists of communities (old syntax)");
+
+function t_clist_new()
+{
+       bt_assert((10, 20).asn = 10);
+       bt_assert((10, 20).data = 20);
+       bt_assert(p23.asn = 2);
+       bt_assert(p23.data = 3);
+
+       clist l;
+       bt_assert(l = -empty-);
+       bt_assert(l !~ [(*,*)]);
+       bt_assert((l ~ [(*,*)]) != (l !~ [(*,*)]));
+
+       bt_assert(-empty- = -empty-);
+
+       l.add( (one,2) );
+       bt_assert(l ~ [(*,*)]);
+       l.add( (2,one+2) );
+       bt_assert(format(l) = "(clist (1,2) (2,3))");
+
+       bt_assert(l.empty = -empty-);
+
+       bt_assert((2,3) ~ l);
+       bt_assert(l ~ [(1,*)]);
+       bt_assert(l ~ [p23]);
+       bt_assert(l ~ [(2,2..3)]);
+       bt_assert(l ~ [(1,1..2)]);
+       bt_assert(l ~ [(1,1)..(1,2)]);
+       bt_assert(l !~ []);
+
+       l.add((2,5));
+       l.add((5,one));
+       l.add((6,one));
+       l.add((one,one));
+       l.delete([(5,1),(6,one),(one,1)]);
+       l.delete([(5,one),(6,one)]);
+       l.filter([(1,*)]);
+       bt_assert(l = -empty-.add((1,2)));
+
+       bt_assert((2,3) !~ l);
+       bt_assert(l !~ [(2,*)]);
+       bt_assert(l !~ [(one,3..6)]);
+       bt_assert(l ~ [(*,*)]);
+
+       l.add((3,one));
+       l.add((one+one+one,one+one));
+       l.add((3,3));
+       l.add((3,4));
+       l.add((3,5));
+       clist l2 = l.filter([(3,*)]);
+       l.delete([(3,2..4)]);
+       bt_assert(l = -empty-.add((1,2)).add((3,1)).add((3,5)));
+       bt_assert(l.len = 3);
+
+       l.add((3,2));
+       l.add((4,5));
+       bt_assert(l = -empty-.add((1,2)).add((3,1)).add((3,5)).add((3,2)).add((4,5)));
+
+       bt_assert(l.len = 5);
+       bt_assert(l ~ [(*,2)]);
+       bt_assert(l ~ [(*,5)]);
+       bt_assert(l ~ [(*, one)]);
+       bt_assert(l !~ [(*,3)]);
+       bt_assert(l !~ [(*,(one+6))]);
+       bt_assert(l !~ [(*, (one+one+one))]);
+
+       bt_assert(l.delete([]) = l);
+       bt_assert(l.filter([]) = -empty-);
+
+       l.delete([(*,(one+onef(3)))]);
+       l.delete([(*,(4+one))]);
+       bt_assert(l = -empty-.add((3,1)));
+
+       l.delete([(*,(onef(5)))]);
+       bt_assert(l = -empty-);
+
+       l2.add((3,6));
+       l = l2.filter([(3,1..4)]);
+       l2.filter([(3,3..6)]);
+
+       #  clist A (10,20,30)
+       bt_assert(l = -empty-.add((3,1)).add((3,2)).add((3,3)).add((3,4)));
+       bt_assert(format(l) = "(clist (3,1) (3,2) (3,3) (3,4))");
+
+       #  clist B (30,40,50)
+       bt_assert(l2 = -empty-.add((3,3)).add((3,4)).add((3,5)).add((3,6)));
+       bt_assert(format(l2) = "(clist (3,3) (3,4) (3,5) (3,6))");
+
+       #  clist A union B
+       clist r = l.add(l2);
+       bt_assert(r = -empty-.add((3,1)).add((3,2)).add((3,3)).add((3,4)).add((3,5)).add((3,6)));
+       bt_assert(format(r) = "(clist (3,1) (3,2) (3,3) (3,4) (3,5) (3,6))");
+
+       #  clist A isect B
+       r = l.filter(l2);
+       bt_assert(r = -empty-.add((3,3)).add((3,4)));
+       bt_assert(format(r) = "(clist (3,3) (3,4))");
+
+       #  clist A \ B
+       r = l.delete(l2);
+       bt_assert(r = -empty-.add((3,1)).add((3,2)));
+       bt_assert(format(r) = "(clist (3,1) (3,2))");
+
+       #  clist in c set
+       r = l.filter([(3,1), (*,2)]);
+       bt_assert(r = -empty-.add((3,1)).add((3,2)));
+       bt_assert(format(r) = "(clist (3,1) (3,2))");
+
+       #  minimim & maximum element
+       r = -empty-.add((2,1)).add((1,3)).add((2,2)).add((3,1)).add((2,3));
+       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_new, "Testing lists of communities (new syntax)");
 
 
 
@@ -1002,11 +1208,12 @@ bt_test_suite(t_ec, "Testing extended communities");
  *     -------------------------------
  */
 
-function t_eclist()
+function t_eclist_old()
 eclist el;
 eclist el2;
 eclist r;
 {
+       # Deprecated syntax
        el = -- empty --;
        el = add(el, (rt, 10, 20));
        el = add(el, (ro, 10.20.30.40, 100));
@@ -1089,7 +1296,99 @@ eclist r;
        bt_assert(x = 3);
 }
 
-bt_test_suite(t_eclist, "Testing lists of extended communities");
+bt_test_suite(t_eclist_old, "Testing lists of extended communities");
+
+
+function t_eclist_new()
+{
+       # New syntax
+       eclist el;
+       bt_assert(el = --empty--);
+       el.add((rt, 10, 20));
+       el.add((ro, 10.20.30.40, 100));
+       el.add((ro, 11.21.31.41.mask(16), 200));
+
+       bt_assert(--empty-- = --empty--);
+       bt_assert(((rt, 10, 20)) !~ --empty--);
+
+       bt_assert(format(el) = "(eclist (rt, 10, 20) (ro, 10.20.30.40, 100) (ro, 11.21.0.0, 200))");
+       bt_assert(el.len = 3);
+       el.delete((rt, 10, 20));
+       el.delete((rt, 10, 30));
+       bt_assert(el = (--empty--).add((ro, 10.20.30.40, 100)).add((ro, 11.21.0.0, 200)));
+
+       bt_assert(el.empty = --empty--);
+
+       el.add((unknown 2, ten, 1));
+       el.add((unknown 5, ten, 1));
+       el.add((rt, ten, one+one));
+       el.add((rt, 10, 3));
+       el.add((rt, 10, 4));
+       el.add((rt, 10, 5));
+       el.add((generic, 0x2000a, 3*ten));
+       el.delete([(rt, 10, 2..ten)]);
+       bt_assert(el = (--empty--).add((ro, 10.20.30.40, 100)).add((ro, 11.21.0.0, 200)).add((rt, 10, 1)).add((unknown 5, 10, 1)).add((rt, 10, 30)));
+
+       el.filter([(rt, 10, *)]);
+       bt_assert(el = (--empty--).add((rt, 10, 1)).add((rt, 10, 30)));
+       bt_assert((rt, 10, 1) ~ el);
+       bt_assert(el ~ [(rt, 10, ten..40)]);
+       bt_assert((rt, 10, 20) !~ el);
+       bt_assert((ro, 10.20.30.40, 100) !~ el);
+       bt_assert(el !~ [(rt, 10, 35..40)]);
+       bt_assert(el !~ [(ro, 10, *)]);
+       bt_assert(el !~ []);
+
+       el.add((rt, 10, 40));
+       eclist el2 = el.filter([(rt, 10, 20..40)] );
+       el2.add((rt, 10, 50));
+
+       bt_assert(el.delete([]) = el);
+       bt_assert(el.filter([]) = --empty--);
+
+       #  eclist A (1,30,40)
+       bt_assert(el = --empty--.add((rt, 10, 1)).add((rt, 10, 30)).add((rt, 10, 40)));
+       bt_assert(format(el) = "(eclist (rt, 10, 1) (rt, 10, 30) (rt, 10, 40))");
+
+       #  eclist B (30,40,50)
+       bt_assert(el2 = --empty--.add((rt, 10, 30)).add((rt, 10, 40)).add((rt, 10, 50)));
+       bt_assert(format(el2) = "(eclist (rt, 10, 30) (rt, 10, 40) (rt, 10, 50))");
+
+       #  eclist A union B
+       eclist r = el2.add(el);
+       bt_assert(r = --empty--.add((rt, 10, 30)).add((rt, 10, 40)).add((rt, 10, 50)).add((rt, 10, 1)));
+       bt_assert(format(r) = "(eclist (rt, 10, 30) (rt, 10, 40) (rt, 10, 50) (rt, 10, 1))");
+
+       #  eclist A isect B
+       r = el.filter(el2);
+       bt_assert(r = --empty--.add((rt, 10, 30)).add((rt, 10, 40)));
+       bt_assert(format(r) = "(eclist (rt, 10, 30) (rt, 10, 40))");
+
+       #  eclist A \ B
+       r = el.delete(el2);
+       bt_assert(r = --empty--.add((rt, 10, 1)));
+       bt_assert(format(r) = "(eclist (rt, 10, 1))");
+
+       #  eclist in ec set
+       r = el.filter([(rt, 10, 1), (rt, 10, 25..30), (ro, 10, 40)]);
+       bt_assert(r = --empty--.add((rt, 10, 1)).add((rt, 10, 30)));
+       bt_assert(format(r) = "(eclist (rt, 10, 1) (rt, 10, 30))");
+
+       #  minimim & maximum element
+       r = --empty--.add((rt, 2, 1)).add((rt, 1, 3)).add((rt, 2, 2)).add((rt, 3, 1)).add((rt, 2, 3));
+       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_new, "Testing lists of extended communities");
 
 
 
@@ -1138,7 +1437,7 @@ function lc mktrip(int a)
        return (a, 2*a, 3*a);
 }
 
-function t_lclist()
+function t_lclist_old()
 lclist ll;
 lclist ll2;
 lclist r;
@@ -1220,7 +1519,92 @@ lclist r;
        bt_assert(mx = r.max);
 }
 
-bt_test_suite(t_lclist, "Testing lists of large communities");
+bt_test_suite(t_lclist_old, "Testing lists of large communities");
+
+
+function t_lclist_new()
+{
+       bt_assert(---empty--- = ---empty---);
+       bt_assert((10, 20, 30) !~ ---empty---);
+
+       bt_assert((10, 20, 30).asn = 10);
+       bt_assert((10, 20, 30).data1 = 20);
+       bt_assert((10, 20, 30).data2 = 30);
+
+       lclist ll;
+       bt_assert(ll = ---empty---);
+       ll.add((ten, 20, 30));
+       ll.add((1000, 2000, 3000));
+       ll.add(mktrip(100000));
+
+       bt_assert(ll.empty = ---empty---);
+       bt_assert(format(ll) = "(lclist (10, 20, 30) (1000, 2000, 3000) (100000, 200000, 300000))");
+       bt_assert(ll.len = 3);
+       bt_assert(ll = ---empty---.add((10, 20, 30)).add((1000, 2000, 3000)).add((100000, 200000, 300000)));
+
+       bt_assert(mktrip(1000) ~ ll);
+       bt_assert(mktrip(100) !~ ll);
+
+       ll.empty;
+       ll.add((10, 10, 10));
+       ll.add((20, 20, 20));
+       ll.add((30, 30, 30));
+
+       lclist ll2;
+       ll2.add((20, 20, 20));
+       ll2.add((30, 30, 30));
+       ll2.add((40, 40, 40));
+
+       bt_assert(ll.delete([]) = ll);
+       bt_assert(ll.filter([]) = ---empty---);
+
+       #  lclist A (10, 20, 30)
+       bt_assert(format(ll) = "(lclist (10, 10, 10) (20, 20, 20) (30, 30, 30))");
+
+       #  lclist B (20, 30, 40)
+       bt_assert(format(ll2) = "(lclist (20, 20, 20) (30, 30, 30) (40, 40, 40))");
+
+       #  lclist A union B
+       lclist r = ll.add(ll2);
+       bt_assert(r = ---empty---.add((10,10,10)).add((20,20,20)).add((30,30,30)).add((40,40,40)));
+       bt_assert(format(r) = "(lclist (10, 10, 10) (20, 20, 20) (30, 30, 30) (40, 40, 40))");
+
+       #  lclist A isect B
+       r = ll.filter(ll2);
+       bt_assert(r = ---empty---.add((20, 20, 20)).add((30, 30, 30)));
+       bt_assert(format(r) = "(lclist (20, 20, 20) (30, 30, 30))");
+
+       #  lclist A \ B
+       r = ll.delete(ll2);
+       bt_assert(r = ---empty---.add((10, 10, 10)));
+       bt_assert(format(r) = "(lclist (10, 10, 10))");
+
+       #  lclist in lc set
+       r = ll.filter([(5..15, *, *), (20, 15..25, *)]);
+       bt_assert(r = ---empty---.add((10, 10, 10)).add((20, 20, 20)));
+       bt_assert(format(r) = "(lclist (10, 10, 10) (20, 20, 20))");
+
+       #  minimim & maximum element
+       r = ---empty---.add((2, 3, 3)).add((1, 2, 3)).add((2, 3, 1)).add((3, 1, 2)).add((2, 1, 3));
+       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_new, "Testing lists of large communities");