diff options
-rw-r--r-- | conf/cf-lex.l | 23 | ||||
-rw-r--r-- | conf/conf.h | 13 | ||||
-rw-r--r-- | conf/confbase.Y | 2 | ||||
-rw-r--r-- | conf/gen_keywords.m4 | 7 | ||||
-rw-r--r-- | doc/bird.sgml | 38 | ||||
-rw-r--r-- | filter/config.Y | 121 | ||||
-rw-r--r-- | filter/data.h | 8 | ||||
-rw-r--r-- | filter/decl.m4 | 96 | ||||
-rw-r--r-- | filter/f-inst.c | 411 | ||||
-rw-r--r-- | filter/test.conf | 400 |
10 files changed, 824 insertions, 295 deletions
diff --git a/conf/cf-lex.l b/conf/cf-lex.l index 95c44181..0357f9ac 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -55,7 +55,6 @@ struct keyword { byte *name; int value; - enum keyword_scope scope; }; #include "conf/keywords.h" @@ -81,7 +80,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; @@ -614,7 +614,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) @@ -743,6 +743,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: @@ -750,6 +752,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 @@ -764,18 +768,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); @@ -799,7 +804,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; } /** diff --git a/conf/conf.h b/conf/conf.h index b7c97ce5..1413cb58 100644 --- a/conf/conf.h +++ b/conf/conf.h @@ -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 diff --git a/conf/confbase.Y b/conf/confbase.Y index e10666f8..c62731b5 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -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 diff --git a/conf/gen_keywords.m4 b/conf/gen_keywords.m4 index 06a38ffd..4e8651f6 100644 --- a/conf/gen_keywords.m4 +++ b/conf/gen_keywords.m4 @@ -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 diff --git a/doc/bird.sgml b/doc/bird.sgml index 46e34c04..3fcf0025 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -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/. diff --git a/filter/config.Y b/filter/config.Y index 5554fd27..09f5dcfa 100644 --- a/filter/config.Y +++ b/filter/config.Y @@ -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--; } @@ -496,7 +516,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 @@ -681,7 +701,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 */ @@ -998,33 +1018,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: @@ -1054,12 +1059,7 @@ 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_EMPTY_LIST); } | '+' EMPTY '+' { $$ = f_const_empty(T_PATH); } @@ -1068,9 +1068,35 @@ term: | '-' '-' '-' EMPTY '-' '-' '-' { $$ = f_const_empty(T_LCLIST); } | '-' '-' '-' '-' EMPTY '-' '-' '-' '-' { $$ = f_const_empty(T_TLVLIST); } | 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); } @@ -1158,7 +1184,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) @@ -1202,9 +1228,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); } diff --git a/filter/data.h b/filter/data.h index 1032eacf..158e2c04 100644 --- a/filter/data.h +++ b/filter/data.h @@ -67,6 +67,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_* */ @@ -283,8 +289,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); diff --git a/filter/decl.m4 b/filter/decl.m4 index 98b72be6..9ee3ae1a 100644 --- a/filter/decl.m4 +++ b/filter/decl.m4 @@ -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. ') @@ -561,6 +616,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[] = " "; diff --git a/filter/f-inst.c b/filter/f-inst.c index 2c79ccbd..1b89fdcb 100644 --- a/filter/f-inst.c +++ b/filter/f-inst.c @@ -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 @@ -489,20 +491,15 @@ 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)); } @@ -560,6 +557,30 @@ 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); @@ -1002,20 +1023,39 @@ 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)); @@ -1051,6 +1091,7 @@ 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)); @@ -1086,6 +1127,7 @@ 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" ); @@ -1094,40 +1136,38 @@ ((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)); @@ -1135,6 +1175,7 @@ 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); @@ -1142,6 +1183,7 @@ 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); @@ -1149,90 +1191,74 @@ 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) { @@ -1371,6 +1397,7 @@ 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))) ]]); @@ -1379,199 +1406,195 @@ 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) ]]); - - } + RESULT(T_LCLIST, ad, [[ lc_set_add(fpool, v1.val.ad, v2.val.lc) ]]); + } - else if (v1.type == T_TLVLIST) - { + INST(FI_TLVLIST_ADD, 2, 1) { + ARG(1, T_TLVLIST); + ARG_ANY(2); + METHOD_CONSTRUCTOR("add"); /* v2.val is either TLV or TLV-set */ if ((v2.type == T_SET) && subtlv_set_type(v2.val.t)) - RESULT_(T_TLVLIST, tl, [[ tlv_set_add(fpool, v1.val.tl, tlv_alloc(fpool, v2.val.t)) ]]); - //runtime("Can't add set"); + RESULT(T_TLVLIST, tl, [[ tlv_set_add(fpool, v1.val.tl, tlv_alloc(fpool, v2.val.t)) ]]); else if (v2.type == T_TLVLIST) - RESULT_(T_TLVLIST, tl, [[ tlv_set_union(fpool, v1.val.tl, v2.val.tl) ]]); + RESULT(T_TLVLIST, tl, [[ tlv_set_union(fpool, v1.val.tl, v2.val.tl) ]]); else runtime("Can't add non-tlv"); - } - - else - runtime("Can't add to non-[e|l]clist"); } - 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) ]]); - } + RESULT(T_LCLIST, ad, [[ lc_set_del(fpool, v1.val.ad, v2.val.lc) ]]); + } + INST(FI_TLVLIST_DEL, 2, 1) { /* (Extended) Community list add or delete */ + ARG(1, T_TLVLIST); + ARG_ANY(2); + METHOD_CONSTRUCTOR("delete"); #if 0 - else if (v1.type == T_TLVLIST) - { - /* v2.val is sub-TLV-set */ - if ((v2.type == T_SET) && subtlv_set_type(v2.val.t)) - RESULT_(T_TLVLIST, tl, [[ tlvlist_filter(fpool, v1.val.tl, &v2, 0) ]]); - else - runtime("Can't delete non-sub-TLV-set"); - } -#endif - + /* v2.val is sub-TLV-set */ + if ((v2.type == T_SET) && subtlv_set_type(v2.val.t)) + RESULT(T_TLVLIST, tl, [[ tlvlist_filter(fpool, v1.val.tl, &v2, 0) ]]); else - runtime("Can't delete in non-[e|l]clist"); +#endif + runtime("Can't delete non-sub-TLV-set"); } - 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"); - } + } + INST(FI_TLVLIST_FILTER, 2, 1) { + ARG(1, T_TLVLIST); + ARG_ANY(2); + METHOD_CONSTRUCTOR("filter"); #if 0 - else if (v1.type == T_TLVLIST) - { /* v2.val is either TLV or sub-TLV-set */ if ((v2.type == T_SET) && subtlv_set_type(v2.val.t) || (v2.type == T_TLVLIST)) - RESULT_(T_TLVLIST, tl, [[ tlvlist_filter(fpool, v1.val.tl, &v2, 1) ]]); + RESULT(T_TLVLIST, tl, [[ tlvlist_filter(fpool, v1.val.tl, &v2, 1) ]]); else - runtime("Can't filter tlv"); - } #endif - - else - runtime("Can't filter non-[e|l]clist"); + runtime("Can't filter tlv"); } INST(FI_ROA_CHECK_IMPLICIT, 0, 1) { /* ROA Check */ diff --git a/filter/test.conf b/filter/test.conf index 33389a17..90f5e694 100644 --- a/filter/test.conf +++ b/filter/test.conf @@ -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"); |