]>
Commit | Line | Data |
---|---|---|
b9d70dc8 PM |
1 | /* |
2 | * BIRD - filters | |
3 | * | |
1c20608e | 4 | * Copyright 1998--2000 Pavel Machek |
b9d70dc8 PM |
5 | * |
6 | * Can be freely distributed and used under the terms of the GNU GPL. | |
c9f8c1a8 | 7 | * |
1877dab2 | 8 | FIXME: priority of ! should be lower |
b9d70dc8 PM |
9 | */ |
10 | ||
11 | CF_HDR | |
12 | ||
2edb31b0 MM |
13 | CF_DEFINES |
14 | ||
b8cc390e | 15 | #define P(a,b) ((a << 8) | b) |
2d496d20 | 16 | |
b8cc390e OZ |
17 | static inline u32 pair(u32 a, u32 b) { return (a << 16) | b; } |
18 | static inline u32 pair_a(u32 p) { return p >> 16; } | |
19 | static inline u32 pair_b(u32 p) { return p & 0xFFFF; } | |
20 | ||
21 | ||
22 | /* | |
23 | * Sets and their items are during parsing handled as lists, linked | |
24 | * through left ptr. The first item in a list also contains a pointer | |
25 | * to the last item in a list (right ptr). For convenience, even items | |
26 | * are handled as one-item lists. Lists are merged by f_merge_items(). | |
27 | */ | |
28 | ||
29 | static inline struct f_tree * | |
30 | f_new_item(struct f_val from, struct f_val to) | |
92a72a4c | 31 | { |
b8cc390e OZ |
32 | struct f_tree *t = f_new_tree(); |
33 | t->right = t; | |
34 | t->from = from; | |
35 | t->to = to; | |
36 | return t; | |
37 | } | |
92a72a4c | 38 | |
b8cc390e OZ |
39 | static inline struct f_tree * |
40 | f_merge_items(struct f_tree *a, struct f_tree *b) | |
41 | { | |
42 | if (!a) return b; | |
43 | a->right->left = b; | |
44 | a->right = b->right; | |
45 | b->right = NULL; | |
46 | return a; | |
47 | } | |
92a72a4c | 48 | |
b8cc390e OZ |
49 | static inline struct f_tree * |
50 | f_new_pair_item(int fa, int ta, int fb, int tb) | |
51 | { | |
52 | struct f_tree *t = f_new_tree(); | |
53 | t->right = t; | |
54 | t->from.type = t->to.type = T_PAIR; | |
55 | t->from.val.i = pair(fa, fb); | |
56 | t->to.val.i = pair(ta, tb); | |
57 | return t; | |
92a72a4c OZ |
58 | } |
59 | ||
b8cc390e OZ |
60 | static inline struct f_tree * |
61 | f_new_pair_set(int fa, int ta, int fb, int tb) | |
4fc36f39 | 62 | { |
b8cc390e OZ |
63 | struct f_tree *lst = NULL; |
64 | int i; | |
c454872f | 65 | |
b8cc390e OZ |
66 | if ((fa == ta) || ((fb == 0) && (tb == 0xFFFF))) |
67 | return f_new_pair_item(fa, ta, fb, tb); | |
68 | ||
69 | if ((ta < fa) || (tb < fb)) | |
70 | cf_error( "From value cannot be higher that To value in pair sets"); | |
c454872f | 71 | |
b8cc390e OZ |
72 | for (i = fa; i <= ta; i++) |
73 | lst = f_merge_items(lst, f_new_pair_item(i, i, fb, tb)); | |
74 | ||
75 | return lst; | |
4fc36f39 OF |
76 | } |
77 | ||
42a0c054 OZ |
78 | #define EC_ALL 0xFFFFFFFF |
79 | ||
80 | static struct f_tree * | |
81 | f_new_ec_item(u32 kind, u32 ipv4_used, u32 key, u32 vf, u32 vt) | |
82 | { | |
83 | u64 fm, to; | |
84 | ||
85 | if (ipv4_used || (key >= 0x10000)) { | |
86 | check_u16(vf); | |
87 | if (vt == EC_ALL) | |
88 | vt = 0xFFFF; | |
89 | else | |
90 | check_u16(vt); | |
91 | } | |
92 | ||
93 | if (kind == EC_GENERIC) { | |
94 | fm = ec_generic(key, vf); | |
95 | to = ec_generic(key, vt); | |
96 | } | |
97 | else if (ipv4_used) { | |
98 | fm = ec_ip4(kind, key, vf); | |
99 | to = ec_ip4(kind, key, vt); | |
100 | } | |
101 | else if (key < 0x10000) { | |
102 | fm = ec_as2(kind, key, vf); | |
103 | to = ec_as2(kind, key, vt); | |
104 | } | |
105 | else { | |
106 | fm = ec_as4(kind, key, vf); | |
107 | to = ec_as4(kind, key, vt); | |
108 | } | |
109 | ||
110 | struct f_tree *t = f_new_tree(); | |
111 | t->right = t; | |
112 | t->from.type = t->to.type = T_EC; | |
113 | t->from.val.ec = fm; | |
114 | t->to.val.ec = to; | |
115 | return t; | |
116 | } | |
117 | ||
118 | static inline struct f_inst * | |
119 | f_generate_empty(struct f_inst *dyn) | |
120 | { | |
121 | struct f_inst *e = f_new_inst(); | |
122 | e->code = 'E'; | |
123 | ||
124 | switch (dyn->aux & EAF_TYPE_MASK) { | |
125 | case EAF_TYPE_AS_PATH: | |
126 | e->aux = T_PATH; | |
127 | break; | |
128 | case EAF_TYPE_INT_SET: | |
129 | e->aux = T_CLIST; | |
130 | break; | |
131 | case EAF_TYPE_EC_SET: | |
132 | e->aux = T_ECLIST; | |
133 | break; | |
134 | default: | |
135 | cf_error("Can't empty that attribute"); | |
136 | } | |
137 | ||
138 | dyn->code = P('e','S'); | |
139 | dyn->a1.p = e; | |
140 | return dyn; | |
141 | } | |
142 | ||
143 | ||
144 | static inline struct f_inst * | |
145 | f_generate_dpair(struct f_inst *t1, struct f_inst *t2) | |
146 | { | |
147 | struct f_inst *rv; | |
148 | ||
149 | if ((t1->code == 'c') && (t2->code == 'c')) { | |
150 | if ((t1->aux != T_INT) || (t2->aux != T_INT)) | |
151 | cf_error( "Can't operate with value of non-integer type in pair constructor"); | |
152 | ||
153 | check_u16(t1->a2.i); | |
154 | check_u16(t2->a2.i); | |
155 | ||
156 | rv = f_new_inst(); | |
157 | rv->code = 'c'; | |
158 | rv->aux = T_PAIR; | |
159 | rv->a2.i = pair(t1->a2.i, t2->a2.i); | |
160 | } | |
161 | else { | |
162 | rv = f_new_inst(); | |
163 | rv->code = P('m', 'p'); | |
164 | rv->a1.p = t1; | |
165 | rv->a2.p = t2; | |
166 | } | |
167 | ||
168 | return rv; | |
169 | } | |
170 | ||
171 | static inline struct f_inst * | |
172 | f_generate_ec(u16 kind, struct f_inst *tk, struct f_inst *tv) | |
173 | { | |
174 | struct f_inst *rv; | |
175 | int c1 = 0, c2 = 0, ipv4_used = 0; | |
176 | u32 key = 0, val2 = 0; | |
177 | ||
178 | if (tk->code == 'c') { | |
179 | c1 = 1; | |
180 | ||
181 | if (tk->aux == T_INT) { | |
182 | ipv4_used = 0; key = tk->a2.i; | |
183 | } | |
184 | else if (tk->aux == T_QUAD) { | |
185 | ipv4_used = 1; key = tk->a2.i; | |
186 | } | |
187 | else | |
188 | cf_error("Can't operate with key of non-integer/IPv4 type in EC constructor"); | |
189 | } | |
190 | ||
191 | #ifndef IPV6 | |
192 | /* IP->Quad implicit conversion */ | |
193 | else if (tk->code == 'C') { | |
194 | c1 = 1; | |
195 | struct f_val *val = tk->a1.p; | |
196 | if (val->type == T_IP) { | |
197 | ipv4_used = 1; key = ipa_to_u32(val->val.px.ip); | |
198 | } | |
199 | else | |
200 | cf_error("Can't operate with key of non-integer/IPv4 type in EC constructor"); | |
201 | } | |
202 | #endif | |
203 | ||
204 | if (tv->code == 'c') { | |
205 | if (tv->aux != T_INT) | |
206 | cf_error("Can't operate with value of non-integer type in EC constructor"); | |
207 | c2 = 1; | |
208 | val2 = tv->a2.i; | |
209 | } | |
210 | ||
211 | if (c1 && c2) { | |
212 | u64 ec; | |
213 | ||
214 | if (kind == EC_GENERIC) { | |
215 | ec = ec_generic(key, val2); | |
216 | } | |
217 | else if (ipv4_used) { | |
218 | check_u16(val2); | |
219 | ec = ec_ip4(kind, key, val2); | |
220 | } | |
221 | else if (key < 0x10000) { | |
222 | ec = ec_as2(kind, key, val2); | |
223 | } | |
224 | else { | |
225 | check_u16(val2); | |
226 | ec = ec_as4(kind, key, val2); | |
227 | } | |
228 | ||
229 | NEW_F_VAL; | |
230 | rv = f_new_inst(); | |
231 | rv->code = 'C'; | |
232 | rv->a1.p = val; | |
233 | val->type = T_EC; | |
234 | val->val.ec = ec; | |
235 | } | |
236 | else { | |
237 | rv = f_new_inst(); | |
238 | rv->code = P('m','c'); | |
239 | rv->aux = kind; | |
240 | rv->a1.p = tk; | |
241 | rv->a2.p = tv; | |
242 | } | |
243 | ||
244 | return rv; | |
78e33c29 | 245 | } |
42a0c054 OZ |
246 | |
247 | ||
248 | ||
b9d70dc8 PM |
249 | CF_DECLS |
250 | ||
e4a73dbf | 251 | CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, |
ba921648 | 252 | ACCEPT, REJECT, ERROR, QUITBIRD, |
42a0c054 OZ |
253 | INT, BOOL, IP, PREFIX, PAIR, QUAD, EC, |
254 | SET, STRING, BGPMASK, BGPPATH, CLIST, ECLIST, | |
7db7b7db | 255 | IF, THEN, ELSE, CASE, |
42a0c054 | 256 | TRUE, FALSE, RT, RO, UNKNOWN, GENERIC, |
e29fa06e | 257 | FROM, GW, NET, MASK, PROTO, SOURCE, SCOPE, CAST, DEST, PREFERENCE, |
36bbfc70 | 258 | LEN, |
f4536657 | 259 | DEFINED, |
7f77e250 | 260 | ADD, DELETE, CONTAINS, RESET, |
7ea5b00f | 261 | PREPEND, FIRST, LAST, MATCH, |
af582c48 | 262 | ROA_CHECK, |
afc54517 | 263 | EMPTY, |
1c20608e | 264 | FILTER, WHERE, EVAL) |
b9d70dc8 | 265 | |
f4536657 | 266 | %nonassoc THEN |
4ed8718a | 267 | %nonassoc ELSE |
f4536657 | 268 | |
42a0c054 | 269 | %type <x> term block cmds cmds_int cmd function_body constant constructor print_one print_list var_list var_listn dynamic_attr static_attr function_call symbol bgp_path_expr |
430da60f | 270 | %type <f> filter filter_body where_filter |
42a0c054 OZ |
271 | %type <i> type break_command pair_expr ec_kind |
272 | %type <i32> pair_atom ec_expr | |
273 | %type <e> pair_item ec_item set_item switch_item set_items switch_items switch_body | |
b1a597e0 | 274 | %type <trie> fprefix_set |
b8cc390e | 275 | %type <v> set_atom switch_atom fprefix fprefix_s fipa |
6dc7a0cb | 276 | %type <s> decls declsn one_decl function_params |
f9491630 | 277 | %type <h> bgp_path bgp_path_tail1 bgp_path_tail2 |
b9d70dc8 PM |
278 | |
279 | CF_GRAMMAR | |
280 | ||
e0f2e42f MM |
281 | CF_ADDTO(conf, filter_def) |
282 | filter_def: | |
b2b7bbfc OZ |
283 | FILTER SYM { $2 = cf_define_symbol($2, SYM_FILTER, NULL); cf_push_scope( $2 ); } |
284 | filter_body { | |
285 | $2->def = $4; | |
ae3e1af2 | 286 | $4->name = $2->name; |
d4d75628 | 287 | DBG( "We have new filter defined (%s)\n", $2->name ); |
ae3e1af2 | 288 | cf_pop_scope(); |
b9d70dc8 PM |
289 | } |
290 | ; | |
291 | ||
1c20608e MM |
292 | CF_ADDTO(conf, filter_eval) |
293 | filter_eval: | |
294 | EVAL term { f_eval_int($2); } | |
295 | ; | |
296 | ||
ba921648 PM |
297 | type: |
298 | INT { $$ = T_INT; } | |
299 | | BOOL { $$ = T_BOOL; } | |
300 | | IP { $$ = T_IP; } | |
301 | | PREFIX { $$ = T_PREFIX; } | |
302 | | PAIR { $$ = T_PAIR; } | |
126683fe | 303 | | QUAD { $$ = T_QUAD; } |
42a0c054 | 304 | | EC { $$ = T_EC; } |
ba921648 | 305 | | STRING { $$ = T_STRING; } |
10a53608 PM |
306 | | BGPMASK { $$ = T_PATH_MASK; } |
307 | | BGPPATH { $$ = T_PATH; } | |
308 | | CLIST { $$ = T_CLIST; } | |
42a0c054 | 309 | | ECLIST { $$ = T_ECLIST; } |
ba921648 PM |
310 | | type SET { |
311 | switch ($1) { | |
b1a597e0 | 312 | case T_INT: |
b1a597e0 | 313 | case T_PAIR: |
126683fe | 314 | case T_QUAD: |
42a0c054 | 315 | case T_EC: |
126683fe | 316 | case T_IP: |
b1a597e0 OZ |
317 | $$ = T_SET; |
318 | break; | |
319 | ||
320 | case T_PREFIX: | |
321 | $$ = T_PREFIX_SET; | |
322 | break; | |
323 | ||
ba921648 | 324 | default: |
a5a947d4 | 325 | cf_error( "You can't create sets of this type." ); |
ba921648 | 326 | } |
b1a597e0 | 327 | } |
ba921648 PM |
328 | ; |
329 | ||
6dc7a0cb PM |
330 | one_decl: |
331 | type SYM { | |
083c43e2 OZ |
332 | struct f_val * val = cfg_alloc(sizeof(struct f_val)); |
333 | val->type = $1; | |
334 | $2 = cf_define_symbol($2, SYM_VARIABLE | $1, val); | |
d4d75628 | 335 | DBG( "New variable %s type %x\n", $2->name, $1 ); |
083c43e2 | 336 | $2->aux2 = NULL; |
6542ece9 | 337 | $$=$2; |
ba921648 PM |
338 | } |
339 | ; | |
340 | ||
6dc7a0cb PM |
341 | /* Decls with ';' at the end */ |
342 | decls: /* EMPTY */ { $$ = NULL; } | |
343 | | one_decl ';' decls { | |
344 | $$ = $1; | |
083c43e2 | 345 | $$->aux2 = $3; |
6dc7a0cb PM |
346 | } |
347 | ; | |
348 | ||
3c989eb4 | 349 | /* Declarations that have no ';' at the end. */ |
6dc7a0cb | 350 | declsn: one_decl { $$ = $1; } |
736fd730 | 351 | | one_decl ';' declsn { |
4515bdba | 352 | $$ = $1; |
083c43e2 | 353 | $$->aux2 = $3; |
6dc7a0cb PM |
354 | } |
355 | ; | |
356 | ||
e0f2e42f | 357 | filter_body: |
ba921648 | 358 | function_body { |
e0f2e42f MM |
359 | struct filter *f = cfg_alloc(sizeof(struct filter)); |
360 | f->name = NULL; | |
ba921648 | 361 | f->root = $1; |
e0f2e42f MM |
362 | $$ = f; |
363 | } | |
364 | ; | |
365 | ||
366 | filter: | |
367 | SYM { | |
a5a947d4 | 368 | if ($1->class != SYM_FILTER) cf_error("No such filter."); |
e0f2e42f MM |
369 | $$ = $1->def; |
370 | } | |
371 | | filter_body | |
372 | ; | |
373 | ||
430da60f MM |
374 | where_filter: |
375 | WHERE term { | |
376 | /* Construct 'IF term THEN ACCEPT; REJECT;' */ | |
377 | struct filter *f = cfg_alloc(sizeof(struct filter)); | |
378 | struct f_inst *i, *acc, *rej; | |
379 | acc = f_new_inst(); /* ACCEPT */ | |
2d496d20 | 380 | acc->code = P('p',','); |
430da60f MM |
381 | acc->a1.p = NULL; |
382 | acc->a2.i = F_ACCEPT; | |
383 | rej = f_new_inst(); /* REJECT */ | |
2d496d20 | 384 | rej->code = P('p',','); |
430da60f MM |
385 | rej->a1.p = NULL; |
386 | rej->a2.i = F_REJECT; | |
387 | i = f_new_inst(); /* IF */ | |
388 | i->code = '?'; | |
389 | i->a1.p = $2; | |
390 | i->a2.p = acc; | |
391 | i->next = rej; | |
392 | f->name = NULL; | |
393 | f->root = i; | |
394 | $$ = f; | |
395 | } | |
396 | ; | |
397 | ||
ba921648 | 398 | function_params: |
d4d75628 | 399 | '(' declsn ')' { DBG( "Have function parameters\n" ); $$=$2; } |
6dc7a0cb | 400 | | '(' ')' { $$=NULL; } |
ba921648 | 401 | ; |
b9d70dc8 | 402 | |
ba921648 PM |
403 | function_body: |
404 | decls '{' cmds '}' { | |
aa461248 OZ |
405 | if ($1) { |
406 | /* Prepend instruction to clear local variables */ | |
407 | $$ = f_new_inst(); | |
408 | $$->code = P('c','v'); | |
409 | $$->a1.p = $1; | |
410 | $$->next = $3; | |
411 | } else | |
412 | $$ = $3; | |
84c7e194 | 413 | } |
ba921648 PM |
414 | ; |
415 | ||
416 | CF_ADDTO(conf, function_def) | |
417 | function_def: | |
bf3eb98e MM |
418 | FUNCTION SYM { DBG( "Beginning of function %s\n", $2->name ); |
419 | $2 = cf_define_symbol($2, SYM_FUNCTION, NULL); | |
420 | cf_push_scope($2); | |
421 | } function_params function_body { | |
422 | $2->def = $5; | |
083c43e2 | 423 | $2->aux2 = $4; |
d4d75628 | 424 | DBG("Hmm, we've got one function here - %s\n", $2->name); |
ae3e1af2 | 425 | cf_pop_scope(); |
ba921648 PM |
426 | } |
427 | ; | |
428 | ||
429 | /* Programs */ | |
430 | ||
5f47c4c1 OZ |
431 | /* Hack: $$ of cmds_int is the last node. |
432 | $$->next of cmds_int is temporary used for the first node */ | |
433 | ||
ba921648 | 434 | cmds: /* EMPTY */ { $$ = NULL; } |
5f47c4c1 OZ |
435 | | cmds_int { $$ = $1->next; $1->next = NULL; } |
436 | ; | |
437 | ||
438 | cmds_int: cmd { $$ = $1; $1->next = $1; } | |
439 | | cmds_int cmd { $$ = $2; $2->next = $1->next ; $1->next = $2; } | |
84c7e194 PM |
440 | ; |
441 | ||
2575593e | 442 | block: |
ba921648 | 443 | cmd { |
2575593e PM |
444 | $$=$1; |
445 | } | |
446 | | '{' cmds '}' { | |
447 | $$=$2; | |
448 | } | |
449 | ; | |
450 | ||
d3dd620b PM |
451 | /* |
452 | * Complex types, their bison value is struct f_val | |
453 | */ | |
e3f2d5fc | 454 | fipa: |
60de3356 | 455 | IPA %prec PREFIX_DUMMY { $$.type = T_IP; $$.val.px.ip = $1; } |
d3dd620b PM |
456 | ; |
457 | ||
b8cc390e OZ |
458 | |
459 | ||
460 | /* | |
461 | * Set constants. They are also used in switch cases. We use separate | |
462 | * nonterminals for switch (set_atom/switch_atom, set_item/switch_item ...) | |
463 | * to elude a collision between symbol (in expr) in set_atom and symbol | |
464 | * as a function call in switch case cmds. | |
465 | */ | |
466 | ||
38506f71 | 467 | set_atom: |
b8cc390e | 468 | expr { $$.type = T_INT; $$.val.i = $1; } |
126683fe | 469 | | RTRID { $$.type = T_QUAD; $$.val.i = $1; } |
92a72a4c | 470 | | fipa { $$ = $1; } |
b8cc390e OZ |
471 | | ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); } |
472 | ; | |
38506f71 | 473 | |
b8cc390e OZ |
474 | switch_atom: |
475 | NUM { $$.type = T_INT; $$.val.i = $1; } | |
476 | | '(' term ')' { $$.type = T_INT; $$.val.i = f_eval_int($2); } | |
477 | | RTRID { $$.type = T_QUAD; $$.val.i = $1; } | |
478 | | fipa { $$ = $1; } | |
479 | | ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); } | |
480 | ; | |
481 | ||
482 | pair_expr: | |
483 | term { $$ = f_eval_int($1); check_u16($$); } | |
484 | ||
485 | pair_atom: | |
486 | pair_expr { $$ = pair($1, $1); } | |
487 | | pair_expr DDOT pair_expr { $$ = pair($1, $3); } | |
488 | | '*' { $$ = 0xFFFF; } | |
489 | ; | |
490 | ||
491 | pair_item: | |
492 | '(' pair_atom ',' pair_atom ')' { | |
493 | $$ = f_new_pair_set(pair_a($2), pair_b($2), pair_a($4), pair_b($4)); | |
995e5894 | 494 | } |
b8cc390e OZ |
495 | | '(' pair_atom ',' pair_atom ')' DDOT '(' pair_expr ',' pair_expr ')' { |
496 | /* Hack: $2 and $4 should be pair_expr, but that would cause shift/reduce conflict */ | |
497 | if ((pair_a($2) != pair_b($2)) || (pair_a($4) != pair_b($4))) | |
498 | cf_error("syntax error"); | |
117e3c4b | 499 | $$ = f_new_pair_item(pair_b($2), $8, pair_b($4), $10); |
995e5894 | 500 | } |
38506f71 PM |
501 | ; |
502 | ||
42a0c054 OZ |
503 | ec_expr: |
504 | term { $$ = f_eval_int($1); } | |
505 | ||
506 | ec_kind: | |
507 | RT { $$ = EC_RT; } | |
508 | | RO { $$ = EC_RO; } | |
509 | | UNKNOWN NUM { $$ = $2; } | |
510 | | GENERIC { $$ = EC_GENERIC; } | |
511 | ; | |
512 | ||
513 | ec_item: | |
514 | '(' ec_kind ',' ec_expr ',' ec_expr ')' { $$ = f_new_ec_item($2, 0, $4, $6, $6); } | |
515 | | '(' ec_kind ',' ec_expr ',' ec_expr DDOT ec_expr ')' { $$ = f_new_ec_item($2, 0, $4, $6, $8); } | |
516 | | '(' ec_kind ',' ec_expr ',' '*' ')' { $$ = f_new_ec_item($2, 0, $4, 0, EC_ALL); } | |
517 | ; | |
518 | ||
b8cc390e OZ |
519 | set_item: |
520 | pair_item | |
42a0c054 | 521 | | ec_item |
b8cc390e OZ |
522 | | set_atom { $$ = f_new_item($1, $1); } |
523 | | set_atom DDOT set_atom { $$ = f_new_item($1, $3); } | |
524 | ; | |
525 | ||
526 | switch_item: | |
527 | pair_item | |
42a0c054 | 528 | | ec_item |
b8cc390e OZ |
529 | | switch_atom { $$ = f_new_item($1, $1); } |
530 | | switch_atom DDOT switch_atom { $$ = f_new_item($1, $3); } | |
531 | ; | |
532 | ||
38506f71 | 533 | set_items: |
b8cc390e OZ |
534 | set_item |
535 | | set_items ',' set_item { $$ = f_merge_items($1, $3); } | |
536 | ; | |
537 | ||
538 | switch_items: | |
539 | switch_item | |
540 | | switch_items ',' switch_item { $$ = f_merge_items($1, $3); } | |
38506f71 PM |
541 | ; |
542 | ||
b1a597e0 OZ |
543 | fprefix_s: |
544 | IPA '/' NUM %prec '/' { | |
545 | if (($3 < 0) || ($3 > MAX_PREFIX_LENGTH) || !ip_is_prefix($1, $3)) cf_error("Invalid network prefix: %I/%d.", $1, $3); | |
546 | $$.type = T_PREFIX; $$.val.px.ip = $1; $$.val.px.len = $3; | |
547 | } | |
548 | ; | |
549 | ||
550 | fprefix: | |
551 | fprefix_s { $$ = $1; } | |
552 | | fprefix_s '+' { $$ = $1; $$.val.px.len |= LEN_PLUS; } | |
553 | | fprefix_s '-' { $$ = $1; $$.val.px.len |= LEN_MINUS; } | |
554 | | fprefix_s '{' NUM ',' NUM '}' { | |
555 | if (! ((0 <= $3) && ($3 <= $5) && ($5 <= MAX_PREFIX_LENGTH))) cf_error("Invalid prefix pattern range: {%d, %d}.", $3, $5); | |
556 | $$ = $1; $$.val.px.len |= LEN_RANGE | ($3 << 16) | ($5 << 8); | |
557 | } | |
558 | ; | |
559 | ||
560 | fprefix_set: | |
7f0d245a OZ |
561 | fprefix { $$ = f_new_trie(cfg_mem); trie_add_fprefix($$, &($1.val.px)); } |
562 | | fprefix_set ',' fprefix { $$ = $1; trie_add_fprefix($$, &($3.val.px)); } | |
b1a597e0 OZ |
563 | ; |
564 | ||
41be4444 | 565 | switch_body: /* EMPTY */ { $$ = NULL; } |
b8cc390e OZ |
566 | | switch_body switch_items ':' cmds { |
567 | /* Fill data fields */ | |
568 | struct f_tree *t; | |
569 | for (t = $2; t; t = t->left) | |
570 | t->data = $4; | |
571 | $$ = f_merge_items($1, $2); | |
41be4444 | 572 | } |
b8cc390e OZ |
573 | | switch_body ELSECOL cmds { |
574 | struct f_tree *t = f_new_tree(); | |
575 | t->from.type = t->to.type = T_VOID; | |
576 | t->right = t; | |
577 | t->data = $3; | |
578 | $$ = f_merge_items($1, t); | |
579 | } | |
41be4444 | 580 | ; |
d3dd620b | 581 | |
e4a73dbf PM |
582 | /* CONST '(' expr ')' { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_INT; $$->a2.i = $3; } */ |
583 | ||
92a72a4c OZ |
584 | bgp_path_expr: |
585 | symbol { $$ = $1; } | |
586 | | '(' term ')' { $$ = $2; } | |
587 | ; | |
588 | ||
f9491630 | 589 | bgp_path: |
cf186034 | 590 | PO bgp_path_tail1 PC { $$ = $2; } |
f9491630 | 591 | | '/' bgp_path_tail2 '/' { $$ = $2; } |
f9491630 OZ |
592 | ; |
593 | ||
594 | bgp_path_tail1: | |
c8a6b9a3 OZ |
595 | NUM bgp_path_tail1 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASN; $$->val = $1; } |
596 | | '*' bgp_path_tail1 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASTERISK; $$->val = 0; } | |
597 | | '?' bgp_path_tail1 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_QUESTION; $$->val = 0; } | |
92a72a4c | 598 | | bgp_path_expr bgp_path_tail1 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASN_EXPR; $$->val = (uintptr_t) $1; } |
f9491630 OZ |
599 | | { $$ = NULL; } |
600 | ; | |
77de6882 | 601 | |
f9491630 | 602 | bgp_path_tail2: |
c8a6b9a3 OZ |
603 | NUM bgp_path_tail2 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASN; $$->val = $1; } |
604 | | '?' bgp_path_tail2 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASTERISK; $$->val = 0; } | |
f9491630 | 605 | | { $$ = NULL; } |
77de6882 PM |
606 | ; |
607 | ||
23b1539b | 608 | constant: |
e4a73dbf | 609 | NUM { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_INT; $$->a2.i = $1; } |
c7b43f33 PM |
610 | | TRUE { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_BOOL; $$->a2.i = 1; } |
611 | | FALSE { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_BOOL; $$->a2.i = 0; } | |
612 | | TEXT { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_STRING; $$->a2.p = $1; } | |
e3f2d5fc | 613 | | fipa { NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; $$->a1.p = val; *val = $1; } |
758458be | 614 | | fprefix_s {NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; $$->a1.p = val; *val = $1; } |
126683fe | 615 | | RTRID { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_QUAD; $$->a2.i = $1; } |
d4d75628 | 616 | | '[' set_items ']' { DBG( "We've got a set here..." ); $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_SET; $$->a2.p = build_tree($2); DBG( "ook\n" ); } |
b1a597e0 | 617 | | '[' fprefix_set ']' { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_PREFIX_SET; $$->a2.p = $2; } |
c7b43f33 | 618 | | ENUM { $$ = f_new_inst(); $$->code = 'c'; $$->aux = $1 >> 16; $$->a2.i = $1 & 0xffff; } |
f9491630 | 619 | | bgp_path { NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; val->type = T_PATH_MASK; val->val.path_mask = $1; $$->a1.p = val; } |
23b1539b PM |
620 | ; |
621 | ||
42a0c054 | 622 | constructor: |
78e33c29 OZ |
623 | '(' term ',' term ')' { $$ = f_generate_dpair($2, $4); } |
624 | | '(' ec_kind ',' term ',' term ')' { $$ = f_generate_ec($2, $4, $6); } | |
42a0c054 OZ |
625 | ; |
626 | ||
92a72a4c | 627 | |
db1326aa MM |
628 | /* |
629 | * Maybe there are no dynamic attributes defined by protocols. | |
630 | * For such cases, we force the dynamic_attr list to contain | |
b8cc390e | 631 | * at least an invalid token, so it is syntantically correct. |
db1326aa MM |
632 | */ |
633 | CF_ADDTO(dynamic_attr, INVALID_TOKEN { $$ = NULL; }) | |
f4536657 | 634 | |
2e18b87d | 635 | rtadot: /* EMPTY, we are not permitted RTA. prefix */ |
6c14255d | 636 | ; |
f4536657 | 637 | |
2d496d20 PM |
638 | function_call: |
639 | SYM '(' var_list ')' { | |
640 | struct symbol *sym; | |
641 | struct f_inst *inst = $3; | |
642 | if ($1->class != SYM_FUNCTION) | |
a5a947d4 | 643 | cf_error("You can't call something which is not a function. Really."); |
d4d75628 | 644 | DBG("You are calling function %s\n", $1->name); |
2d496d20 PM |
645 | $$ = f_new_inst(); |
646 | $$->code = P('c','a'); | |
647 | $$->a1.p = inst; | |
083c43e2 OZ |
648 | $$->a2.p = $1->def; |
649 | sym = $1->aux2; | |
2d496d20 PM |
650 | while (sym || inst) { |
651 | if (!sym || !inst) | |
a5a947d4 | 652 | cf_error("Wrong number of arguments for function %s.", $1->name); |
d4d75628 | 653 | DBG( "You should pass parameter called %s\n", sym->name); |
2d496d20 | 654 | inst->a1.p = sym; |
083c43e2 | 655 | sym = sym->aux2; |
2d496d20 PM |
656 | inst = inst->next; |
657 | } | |
658 | } | |
659 | ; | |
660 | ||
92a72a4c OZ |
661 | symbol: |
662 | SYM { | |
663 | $$ = f_new_inst(); | |
664 | switch ($1->class) { | |
665 | case SYM_NUMBER: | |
666 | $$ = f_new_inst(); | |
667 | $$->code = 'c'; | |
668 | $$->aux = T_INT; | |
669 | $$->a2.i = $1->aux; | |
670 | break; | |
671 | case SYM_IPA: | |
672 | { NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; $$->a1.p = val; val->type = T_IP; val->val.px.ip = * (ip_addr *) ($1->def); } | |
673 | break; | |
674 | case SYM_VARIABLE | T_BOOL: | |
675 | case SYM_VARIABLE | T_INT: | |
676 | case SYM_VARIABLE | T_PAIR: | |
126683fe | 677 | case SYM_VARIABLE | T_QUAD: |
42a0c054 | 678 | case SYM_VARIABLE | T_EC: |
92a72a4c OZ |
679 | case SYM_VARIABLE | T_STRING: |
680 | case SYM_VARIABLE | T_IP: | |
681 | case SYM_VARIABLE | T_PREFIX: | |
682 | case SYM_VARIABLE | T_PREFIX_SET: | |
683 | case SYM_VARIABLE | T_SET: | |
684 | case SYM_VARIABLE | T_PATH: | |
685 | case SYM_VARIABLE | T_PATH_MASK: | |
686 | case SYM_VARIABLE | T_CLIST: | |
42a0c054 | 687 | case SYM_VARIABLE | T_ECLIST: |
5004d02c | 688 | $$->code = 'V'; |
92a72a4c | 689 | $$->a1.p = $1->def; |
5004d02c | 690 | $$->a2.p = $1->name; |
92a72a4c OZ |
691 | break; |
692 | default: | |
693 | cf_error("%s: variable expected.", $1->name ); | |
694 | } | |
695 | } | |
696 | ||
2bdb5e00 | 697 | static_attr: |
0dc4431c | 698 | FROM { $$ = f_new_inst(); $$->aux = T_IP; $$->a2.i = OFFSETOF(struct rta, from); $$->a1.i = 1; } |
26c09e1d | 699 | |
0dc4431c | 700 | | GW { $$ = f_new_inst(); $$->aux = T_IP; $$->a2.i = OFFSETOF(struct rta, gw); $$->a1.i = 1; } |
26c09e1d | 701 | | NET { $$ = f_new_inst(); $$->aux = T_PREFIX; $$->a2.i = 0x12345678; /* This is actually ok - T_PREFIX is special-cased. */ } |
e29fa06e | 702 | | PROTO { $$ = f_new_inst(); $$->aux = T_STRING; $$->a2.i = 0x12345678; /* T_STRING is also special-cased. */ } |
26c09e1d | 703 | | SOURCE { $$ = f_new_inst(); $$->aux = T_ENUM_RTS; $$->a2.i = OFFSETOF(struct rta, source); } |
0dc4431c | 704 | | SCOPE { $$ = f_new_inst(); $$->aux = T_ENUM_SCOPE; $$->a2.i = OFFSETOF(struct rta, scope); $$->a1.i = 1; } |
26c09e1d | 705 | | CAST { $$ = f_new_inst(); $$->aux = T_ENUM_RTC; $$->a2.i = OFFSETOF(struct rta, cast); } |
182a7895 | 706 | | DEST { $$ = f_new_inst(); $$->aux = T_ENUM_RTD; $$->a2.i = OFFSETOF(struct rta, dest); $$->a1.i = 1; } |
2bdb5e00 PM |
707 | ; |
708 | ||
84c7e194 | 709 | term: |
f4536657 | 710 | '(' term ')' { $$ = $2; } |
d4d75628 | 711 | | term '+' term { $$ = f_new_inst(); $$->code = '+'; $$->a1.p = $1; $$->a2.p = $3; } |
c5a06f65 PM |
712 | | term '-' term { $$ = f_new_inst(); $$->code = '-'; $$->a1.p = $1; $$->a2.p = $3; } |
713 | | term '*' term { $$ = f_new_inst(); $$->code = '*'; $$->a1.p = $1; $$->a2.p = $3; } | |
714 | | term '/' term { $$ = f_new_inst(); $$->code = '/'; $$->a1.p = $1; $$->a2.p = $3; } | |
5f4aee76 PM |
715 | | term AND term { $$ = f_new_inst(); $$->code = '&'; $$->a1.p = $1; $$->a2.p = $3; } |
716 | | term OR term { $$ = f_new_inst(); $$->code = '|'; $$->a1.p = $1; $$->a2.p = $3; } | |
2d496d20 | 717 | | term '=' term { $$ = f_new_inst(); $$->code = P('=','='); $$->a1.p = $1; $$->a2.p = $3; } |
d4d75628 PM |
718 | | term NEQ term { $$ = f_new_inst(); $$->code = P('!','='); $$->a1.p = $1; $$->a2.p = $3; } |
719 | | term '<' term { $$ = f_new_inst(); $$->code = '<'; $$->a1.p = $1; $$->a2.p = $3; } | |
720 | | term LEQ term { $$ = f_new_inst(); $$->code = P('<','='); $$->a1.p = $1; $$->a2.p = $3; } | |
721 | | term '>' term { $$ = f_new_inst(); $$->code = '<'; $$->a1.p = $3; $$->a2.p = $1; } | |
722 | | term GEQ term { $$ = f_new_inst(); $$->code = P('<','='); $$->a1.p = $3; $$->a2.p = $1; } | |
723 | | term '~' term { $$ = f_new_inst(); $$->code = '~'; $$->a1.p = $1; $$->a2.p = $3; } | |
995e5894 | 724 | | '!' term { $$ = f_new_inst(); $$->code = '!'; $$->a1.p = $2; } |
2d496d20 | 725 | | DEFINED '(' term ')' { $$ = f_new_inst(); $$->code = P('d','e'); $$->a1.p = $3; } |
23b1539b | 726 | |
92a72a4c | 727 | | symbol { $$ = $1; } |
1183b6b2 | 728 | | constant { $$ = $1; } |
42a0c054 | 729 | | constructor { $$ = $1; } |
4515bdba | 730 | |
0dc4431c | 731 | | PREFERENCE { $$ = f_new_inst(); $$->code = 'P'; } |
2bdb5e00 PM |
732 | |
733 | | rtadot static_attr { $$ = $2; $$->code = 'a'; } | |
fe613ecd | 734 | |
db1326aa | 735 | | rtadot dynamic_attr { $$ = $2; $$->code = P('e','a'); } |
36bbfc70 | 736 | |
2d496d20 | 737 | | term '.' IP { $$ = f_new_inst(); $$->code = P('c','p'); $$->a1.p = $1; $$->aux = T_IP; } |
684c6f5a | 738 | | term '.' LEN { $$ = f_new_inst(); $$->code = 'L'; $$->a1.p = $1; } |
2d496d20 | 739 | | term '.' MASK '(' term ')' { $$ = f_new_inst(); $$->code = P('i','M'); $$->a1.p = $1; $$->a2.p = $5; } |
7ea5b00f OZ |
740 | | term '.' FIRST { $$ = f_new_inst(); $$->code = P('a','f'); $$->a1.p = $1; } |
741 | | term '.' LAST { $$ = f_new_inst(); $$->code = P('a','l'); $$->a1.p = $1; } | |
7f77e250 PM |
742 | |
743 | /* Communities */ | |
10a53608 PM |
744 | /* This causes one shift/reduce conflict |
745 | | rtadot dynamic_attr '.' ADD '(' term ')' { } | |
746 | | rtadot dynamic_attr '.' DELETE '(' term ')' { } | |
747 | | rtadot dynamic_attr '.' CONTAINS '(' term ')' { } | |
a2d15746 | 748 | | rtadot dynamic_attr '.' RESET{ } |
10a53608 | 749 | */ |
7f77e250 | 750 | |
e399b6f6 | 751 | | '+' EMPTY '+' { $$ = f_new_inst(); $$->code = 'E'; $$->aux = T_PATH; } |
9c400ec9 | 752 | | '-' EMPTY '-' { $$ = f_new_inst(); $$->code = 'E'; $$->aux = T_CLIST; } |
42a0c054 | 753 | | '-' '-' EMPTY '-' '-' { $$ = f_new_inst(); $$->code = 'E'; $$->aux = T_ECLIST; } |
e399b6f6 | 754 | | PREPEND '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('A','p'); $$->a1.p = $3; $$->a2.p = $5; } |
4444ed2b PM |
755 | | ADD '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'a'; } |
756 | | DELETE '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'd'; } | |
e08d2ff0 | 757 | | FILTER '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'f'; } |
afc54517 | 758 | |
af582c48 OZ |
759 | | ROA_CHECK '(' SYM ')' { $$ = f_generate_roa_check($3, NULL, NULL); } |
760 | | ROA_CHECK '(' SYM ',' term ',' term ')' { $$ = f_generate_roa_check($3, $5, $7); } | |
761 | ||
a2d15746 | 762 | /* | term '.' LEN { $$->code = P('P','l'); } */ |
7f77e250 | 763 | |
995e5894 PM |
764 | /* function_call is inlined here */ |
765 | | SYM '(' var_list ')' { | |
766 | struct symbol *sym; | |
767 | struct f_inst *inst = $3; | |
768 | if ($1->class != SYM_FUNCTION) | |
a5a947d4 | 769 | cf_error("You can't call something which is not a function. Really."); |
995e5894 PM |
770 | DBG("You are calling function %s\n", $1->name); |
771 | $$ = f_new_inst(); | |
772 | $$->code = P('c','a'); | |
773 | $$->a1.p = inst; | |
083c43e2 OZ |
774 | $$->a2.p = $1->def; |
775 | sym = $1->aux2; | |
995e5894 PM |
776 | while (sym || inst) { |
777 | if (!sym || !inst) | |
a5a947d4 | 778 | cf_error("Wrong number of arguments for function %s.", $1->name); |
995e5894 PM |
779 | DBG( "You should pass parameter called %s\n", sym->name); |
780 | inst->a1.p = sym; | |
083c43e2 | 781 | sym = sym->aux2; |
995e5894 PM |
782 | inst = inst->next; |
783 | } | |
784 | } | |
ba921648 PM |
785 | ; |
786 | ||
787 | break_command: | |
03e3d184 MM |
788 | QUITBIRD { $$ = F_QUITBIRD; } |
789 | | ACCEPT { $$ = F_ACCEPT; } | |
790 | | REJECT { $$ = F_REJECT; } | |
791 | | ERROR { $$ = F_ERROR; } | |
792 | | PRINT { $$ = F_NOP; } | |
793 | | PRINTN { $$ = F_NONL; } | |
ba921648 PM |
794 | ; |
795 | ||
23b1539b | 796 | print_one: |
2db3b288 | 797 | term { $$ = f_new_inst(); $$->code = 'p'; $$->a1.p = $1; $$->a2.p = NULL; } |
23b1539b PM |
798 | ; |
799 | ||
800 | print_list: /* EMPTY */ { $$ = NULL; } | |
995e5894 PM |
801 | | print_one { $$ = $1; } |
802 | | print_one ',' print_list { | |
23b1539b | 803 | if ($1) { |
995e5894 | 804 | $1->next = $3; |
23b1539b | 805 | $$ = $1; |
995e5894 | 806 | } else $$ = $3; |
23b1539b PM |
807 | } |
808 | ; | |
809 | ||
6dc7a0cb | 810 | var_listn: term { |
d3dd620b PM |
811 | $$ = f_new_inst(); |
812 | $$->code = 's'; | |
813 | $$->a1.p = NULL; | |
814 | $$->a2.p = $1; | |
815 | $$->next = NULL; | |
816 | } | |
6dc7a0cb | 817 | | term ',' var_listn { |
6542ece9 PM |
818 | $$ = f_new_inst(); |
819 | $$->code = 's'; | |
820 | $$->a1.p = NULL; | |
821 | $$->a2.p = $1; | |
822 | $$->next = $3; | |
823 | } | |
824 | ; | |
825 | ||
6dc7a0cb PM |
826 | var_list: /* EMPTY */ { $$ = NULL; } |
827 | | var_listn { $$ = $1; } | |
828 | ; | |
829 | ||
23b1539b | 830 | cmd: |
49955645 MM |
831 | IF term THEN block { |
832 | $$ = f_new_inst(); | |
833 | $$->code = '?'; | |
834 | $$->a1.p = $2; | |
835 | $$->a2.p = $4; | |
23b1539b | 836 | } |
49955645 MM |
837 | | IF term THEN block ELSE block { |
838 | struct f_inst *i = f_new_inst(); | |
839 | i->code = '?'; | |
840 | i->a1.p = $2; | |
841 | i->a2.p = $4; | |
23b1539b PM |
842 | $$ = f_new_inst(); |
843 | $$->code = '?'; | |
49955645 MM |
844 | $$->a1.p = i; |
845 | $$->a2.p = $6; | |
23b1539b | 846 | } |
ba921648 | 847 | | SYM '=' term ';' { |
84c7e194 | 848 | $$ = f_new_inst(); |
d4d75628 | 849 | DBG( "Ook, we'll set value\n" ); |
ba921648 | 850 | if (($1->class & ~T_MASK) != SYM_VARIABLE) |
a5a947d4 | 851 | cf_error( "You may set only variables." ); |
23b1539b | 852 | $$->code = 's'; |
2db3b288 PM |
853 | $$->a1.p = $1; |
854 | $$->a2.p = $3; | |
b9d70dc8 | 855 | } |
2d496d20 PM |
856 | | RETURN term ';' { |
857 | $$ = f_new_inst(); | |
d4d75628 | 858 | DBG( "Ook, we'll return the value\n" ); |
2d496d20 PM |
859 | $$->code = 'r'; |
860 | $$->a1.p = $2; | |
861 | } | |
db1326aa | 862 | | rtadot dynamic_attr '=' term ';' { |
6c14255d | 863 | $$ = $2; |
0dc4431c | 864 | $$->code = P('e','S'); |
6c14255d | 865 | $$->a1.p = $4; |
f31156ca | 866 | } |
0dc4431c PM |
867 | | rtadot static_attr '=' term ';' { |
868 | $$ = $2; | |
869 | if (!$$->a1.i) | |
870 | cf_error( "This static attribute is read-only."); | |
871 | $$->code = P('a','S'); | |
872 | $$->a1.p = $4; | |
873 | } | |
874 | | PREFERENCE '=' term ';' { | |
875 | $$ = f_new_inst(); | |
876 | $$->code = P('P','S'); | |
877 | $$->a1.p = $3; | |
878 | } | |
db1326aa | 879 | | UNSET '(' rtadot dynamic_attr ')' ';' { |
6c14255d | 880 | $$ = $4; |
9f4929e7 | 881 | $$->aux = EAF_TYPE_UNDEF | EAF_TEMP; |
2d496d20 | 882 | $$->code = P('e','S'); |
48f9e019 | 883 | $$->a1.p = NULL; |
c7b43f33 | 884 | } |
2d496d20 PM |
885 | | break_command print_list ';' { $$ = f_new_inst(); $$->code = P('p',','); $$->a1.p = $2; $$->a2.i = $1; } |
886 | | function_call ';' { $$ = $1; } | |
7db7b7db PM |
887 | | CASE term '{' switch_body '}' { |
888 | $$ = f_new_inst(); | |
2d496d20 | 889 | $$->code = P('S','W'); |
7db7b7db | 890 | $$->a1.p = $2; |
41be4444 | 891 | $$->a2.p = build_tree( $4 ); |
7db7b7db | 892 | } |
7d6eebae PM |
893 | |
894 | ||
42a0c054 | 895 | | rtadot dynamic_attr '.' EMPTY ';' { $$ = f_generate_empty($2); } |
7d6eebae | 896 | | rtadot dynamic_attr '.' PREPEND '(' term ')' ';' { $$ = f_generate_complex( P('A','p'), 'x', $2, $6 ); } |
42a0c054 OZ |
897 | | rtadot dynamic_attr '.' ADD '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'a', $2, $6 ); } |
898 | | rtadot dynamic_attr '.' DELETE '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'd', $2, $6 ); } | |
899 | | rtadot dynamic_attr '.' FILTER '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'f', $2, $6 ); } | |
b9d70dc8 PM |
900 | ; |
901 | ||
902 | CF_END |