Sets of integers now actually work. Sets of IP will work as soon as
compare function is ready.
This commit is contained in:
parent
01bd7759b2
commit
38506f71b0
8 changed files with 254 additions and 41 deletions
10
bird.conf
10
bird.conf
|
@ -20,6 +20,16 @@ int i;
|
||||||
if 1 <= 1 then print "test 3 passed"; else { print "*** FAIL: test 3"; }
|
if 1 <= 1 then print "test 3 passed"; else { print "*** FAIL: test 3"; }
|
||||||
if 1234 < 1234 then { print "*** FAIL: test 4"; quitbird; } else print "test 4 passed";
|
if 1234 < 1234 then { print "*** FAIL: test 4"; quitbird; } else print "test 4 passed";
|
||||||
|
|
||||||
|
print "Testing IP addresses: 1.2.3.4 = " 1.2.3.4;
|
||||||
|
print "Testing sets of ints = " [ 1, 2, 3 ];
|
||||||
|
print "Testing sets of ints = " [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ];
|
||||||
|
print "Testing sets of IPs = " [ 1.2.3.4, 2.3.4.5, 3.4.5.6 ];
|
||||||
|
print "Sets: true = " 1 ~ [ 1, 2, 3 ];
|
||||||
|
print " false = " 1 ~ [ 2, 3, 4 ];
|
||||||
|
print "a..b: true = " 5 ~ [ 4 .. 7 ];
|
||||||
|
print " false = " 5 ~ [ 2, 3, 4, 7..11 ];
|
||||||
|
print "IPsets: true = " 1.2.3.4 ~ [ 1.2.3.3..1.2.3.5 ];
|
||||||
|
print " false = " 1.2.3.4 ~ [ 1.2.3.3, 1.2.3.5 ];
|
||||||
|
|
||||||
print "done";
|
print "done";
|
||||||
quitbird;
|
quitbird;
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
%{
|
%{
|
||||||
#undef REJECT /* Avoid name clashes */
|
#undef REJECT /* Avoid name clashes */
|
||||||
|
|
||||||
|
#include "filter/filter.h"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -103,7 +105,7 @@ WHITE [ \t]
|
||||||
return SYM;
|
return SYM;
|
||||||
}
|
}
|
||||||
|
|
||||||
[={}:;,()+*/%-<>~] {
|
[={}:;,()+*/%-<>~\[\]] {
|
||||||
return yytext[0];
|
return yytext[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ CF_DECLS
|
||||||
char *t;
|
char *t;
|
||||||
struct f_inst *x;
|
struct f_inst *x;
|
||||||
struct filter *f;
|
struct filter *f;
|
||||||
|
struct f_tree *e;
|
||||||
|
struct f_val v;
|
||||||
}
|
}
|
||||||
|
|
||||||
%token END
|
%token END
|
||||||
|
@ -37,7 +39,7 @@ CF_DECLS
|
||||||
|
|
||||||
%type <i> expr bool pxlen
|
%type <i> expr bool pxlen
|
||||||
|
|
||||||
%nonassoc '=' '<' '>'
|
%nonassoc '=' '<' '>' '~' ELSE IF
|
||||||
%left '+' '-'
|
%left '+' '-'
|
||||||
%left '*' '/' '%'
|
%left '*' '/' '%'
|
||||||
%left '!'
|
%left '!'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
source=f-util.c filter.c
|
source=f-util.c filter.c tree.c
|
||||||
root-rel=../
|
root-rel=../
|
||||||
dir-name=filter
|
dir-name=filter
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ CF_HDR
|
||||||
|
|
||||||
CF_DECLS
|
CF_DECLS
|
||||||
|
|
||||||
CF_KEYWORDS(FUNCTION, PRINTDEBUG, PRINT, CONST, PUTS,
|
CF_KEYWORDS(FUNCTION, PRINT, CONST,
|
||||||
ACCEPT, REJECT, ERROR, QUITBIRD,
|
ACCEPT, REJECT, ERROR, QUITBIRD,
|
||||||
INT, BOOL, IP, PREFIX, PAIR, SET, STRING,
|
INT, BOOL, IP, PREFIX, PAIR, SET, STRING,
|
||||||
IF, THEN, ELSE,
|
IF, THEN, ELSE,
|
||||||
|
@ -29,6 +29,8 @@ CF_KEYWORDS(FUNCTION, PRINTDEBUG, PRINT, CONST, PUTS,
|
||||||
%type <x> term block cmds cmd function_body ifthen constant print_one print_list
|
%type <x> term block cmds cmd function_body ifthen constant print_one print_list
|
||||||
%type <f> filter filter_body
|
%type <f> filter filter_body
|
||||||
%type <i> type break_command
|
%type <i> type break_command
|
||||||
|
%type <e> set_item set_items
|
||||||
|
%type <v> set_atom
|
||||||
|
|
||||||
CF_GRAMMAR
|
CF_GRAMMAR
|
||||||
|
|
||||||
|
@ -128,12 +130,29 @@ block:
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
set_atom:
|
||||||
|
NUM { $$.type = T_INT; $$.val.i = $1; }
|
||||||
|
| IPA { $$.type = T_IP; $$.val.ip = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
|
set_item:
|
||||||
|
set_atom { $$ = f_new_tree(); $$->from = $$->to = $1 }
|
||||||
|
| set_atom '.' '.' set_atom { $$ = f_new_tree(); $$->from = $1; $$->to = $4; }
|
||||||
|
;
|
||||||
|
|
||||||
|
set_items:
|
||||||
|
set_item { $$ = $1; }
|
||||||
|
| set_items ',' set_item { $$ = $3; $$->left = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
constant:
|
constant:
|
||||||
CONST '(' expr ')' { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_INT; $$->a2.i = $3; }
|
CONST '(' expr ')' { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_INT; $$->a2.i = $3; }
|
||||||
| NUM { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_INT; $$->a2.i = $1; }
|
| NUM { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_INT; $$->a2.i = $1; }
|
||||||
| TRUE { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_BOOL; $$->a2.i = 1; }
|
| TRUE { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_BOOL; $$->a2.i = 1; }
|
||||||
| FALSE { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_BOOL; $$->a2.i = 0; }
|
| FALSE { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_BOOL; $$->a2.i = 0; }
|
||||||
| TEXT { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_STRING; $$->a2.p = $1; }
|
| TEXT { $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_STRING; $$->a2.p = $1; }
|
||||||
|
| IPA { struct f_val * val; val = cfg_alloc(sizeof(struct f_val)); $$ = f_new_inst(); $$->code = 'C'; $$->a1.p = val; val->type = T_IP; val->val.ip = $1; }
|
||||||
|
| '[' set_items ']' { printf( "We've got a set here..." ); $$ = f_new_inst(); $$->code = 'c'; $$->a1.i = T_SET; $$->a2.p = build_tree($2); printf( "ook\n" ); }
|
||||||
;
|
;
|
||||||
|
|
||||||
term:
|
term:
|
||||||
|
@ -145,6 +164,7 @@ term:
|
||||||
| term '<' '=' term { $$ = f_new_inst(); $$->code = '<='; $$->a1.p = $1; $$->a2.p = $4; }
|
| term '<' '=' term { $$ = f_new_inst(); $$->code = '<='; $$->a1.p = $1; $$->a2.p = $4; }
|
||||||
| term '>' term { $$ = f_new_inst(); $$->code = '<'; $$->a1.p = $3; $$->a2.p = $1; }
|
| term '>' term { $$ = f_new_inst(); $$->code = '<'; $$->a1.p = $3; $$->a2.p = $1; }
|
||||||
| term '>' '=' term { $$ = f_new_inst(); $$->code = '<='; $$->a1.p = $4; $$->a2.p = $1; }
|
| term '>' '=' term { $$ = f_new_inst(); $$->code = '<='; $$->a1.p = $4; $$->a2.p = $1; }
|
||||||
|
| term '~' term { $$ = f_new_inst(); $$->code = '~'; $$->a1.p = $1; $$->a2.p = $3; }
|
||||||
|
|
||||||
| SYM {
|
| SYM {
|
||||||
$$ = f_new_inst();
|
$$ = f_new_inst();
|
||||||
|
|
113
filter/filter.c
113
filter/filter.c
|
@ -16,6 +16,7 @@
|
||||||
#include "lib/lists.h"
|
#include "lib/lists.h"
|
||||||
#include "lib/resource.h"
|
#include "lib/resource.h"
|
||||||
#include "lib/socket.h"
|
#include "lib/socket.h"
|
||||||
|
#include "lib/string.h"
|
||||||
#include "nest/route.h"
|
#include "nest/route.h"
|
||||||
#include "nest/protocol.h"
|
#include "nest/protocol.h"
|
||||||
#include "nest/iface.h"
|
#include "nest/iface.h"
|
||||||
|
@ -43,11 +44,59 @@ struct f_inst *startup_func = NULL;
|
||||||
if (v1.type != v2.type) \
|
if (v1.type != v2.type) \
|
||||||
runtime( "Can not operate with values of incompatible types" );
|
runtime( "Can not operate with values of incompatible types" );
|
||||||
|
|
||||||
|
#define CMP_ERROR 999
|
||||||
|
|
||||||
|
/* Compare two values, returns -1, 0, 1 compared, ERROR 999 */
|
||||||
|
int
|
||||||
|
val_compare(struct f_val v1, struct f_val v2)
|
||||||
|
{
|
||||||
|
switch (v1.type) {
|
||||||
|
case T_INT:
|
||||||
|
if (v1.val.i == v2.val.i) return 0;
|
||||||
|
if (v1.val.i < v2.val.i) return -1;
|
||||||
|
return 1;
|
||||||
|
default: return CMP_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tree_print(struct f_tree *t)
|
||||||
|
{
|
||||||
|
if (!t) {
|
||||||
|
printf( "() " );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
printf( "[ " );
|
||||||
|
tree_print( t->left );
|
||||||
|
printf( ", " ); val_print( t->from ); printf( ".." ); val_print( t->to ); printf( ", " );
|
||||||
|
tree_print( t->right );
|
||||||
|
printf( "] " );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
val_print(struct f_val v)
|
||||||
|
{
|
||||||
|
char buf[2048];
|
||||||
|
#define PRINTF(a...) bsnprintf( buf, 2040, a )
|
||||||
|
buf[0] = 0;
|
||||||
|
switch (v.type) {
|
||||||
|
case T_VOID: PRINTF( "(void)" ); break;
|
||||||
|
case T_BOOL: PRINTF( v.val.i ? "TRUE" : "FALSE" ); break;
|
||||||
|
case T_INT: PRINTF( "%d ", v.val.i ); break;
|
||||||
|
case T_STRING: PRINTF( "%s", v.val.s ); break;
|
||||||
|
case T_IP: PRINTF( "%I", v.val.ip ); break;
|
||||||
|
case T_SET: tree_print( v.val.t ); PRINTF( "\n" ); break;
|
||||||
|
default: PRINTF( "[unknown type %x]", v.type );
|
||||||
|
}
|
||||||
|
printf( buf );
|
||||||
|
}
|
||||||
|
|
||||||
static struct f_val
|
static struct f_val
|
||||||
interpret(struct f_inst *what)
|
interpret(struct f_inst *what)
|
||||||
{
|
{
|
||||||
struct symbol *sym;
|
struct symbol *sym;
|
||||||
struct f_val v1, v2, res;
|
struct f_val v1, v2, res;
|
||||||
|
int i,j,k;
|
||||||
|
|
||||||
res.type = T_VOID;
|
res.type = T_VOID;
|
||||||
if (!what)
|
if (!what)
|
||||||
|
@ -80,38 +129,32 @@ interpret(struct f_inst *what)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Relational operators */
|
/* Relational operators */
|
||||||
case '!=':
|
|
||||||
case '==':
|
#define COMPARE(x) \
|
||||||
TWOARGS_C;
|
TWOARGS_C; \
|
||||||
res.type = T_BOOL;
|
res.type = T_BOOL; \
|
||||||
switch (v1.type) {
|
i = val_compare(v1, v2); \
|
||||||
case T_VOID: runtime( "Can not operate with values of type void" );
|
if (i==CMP_ERROR) \
|
||||||
case T_INT: res.val.i = (v1.val.i == v2.val.i); break;
|
runtime( "Error in comparation" ); \
|
||||||
default: runtime( "Usage of unknown type" );
|
res.val.i = (x); \
|
||||||
}
|
|
||||||
if (what->code == '!=')
|
|
||||||
res.val.i = !(res.val.i);
|
|
||||||
break;
|
|
||||||
case '<':
|
|
||||||
TWOARGS_C;
|
|
||||||
res.type = T_BOOL;
|
|
||||||
switch (v1.type) {
|
|
||||||
case T_VOID: runtime( "Can not operate with values of type void" );
|
|
||||||
case T_INT: res.val.i = (v1.val.i < v2.val.i); break;
|
|
||||||
default: runtime( "Usage of unknown type" );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '<=':
|
|
||||||
TWOARGS_C;
|
|
||||||
res.type = T_BOOL;
|
|
||||||
switch (v1.type) {
|
|
||||||
case T_VOID: runtime( "Can not operate with values of type void" );
|
|
||||||
case T_INT: res.val.i = (v1.val.i <= v2.val.i); break;
|
|
||||||
default: runtime( "Usage of unknown type" );
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Set */
|
case '!=': COMPARE(i!=0);
|
||||||
|
case '==': COMPARE(i==0);
|
||||||
|
case '<': COMPARE(i==-1);
|
||||||
|
case '<=': COMPARE(i!=1);
|
||||||
|
|
||||||
|
case '~':
|
||||||
|
TWOARGS;
|
||||||
|
res.type = T_BOOL;
|
||||||
|
if (((v1.type == T_INT) || (v1.type == T_IP)) && (v2.type == T_SET)) {
|
||||||
|
res.val.i = !! find_tree(v2.val.t, v1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
runtime( "~ applied on unknown type pair" );
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Set to consant, a1 = type, a2 = value */
|
||||||
case 's':
|
case 's':
|
||||||
ARG(v2, a2.p);
|
ARG(v2, a2.p);
|
||||||
sym = what->a1.p;
|
sym = what->a1.p;
|
||||||
|
@ -129,18 +172,16 @@ interpret(struct f_inst *what)
|
||||||
res.type = what->a1.i;
|
res.type = what->a1.i;
|
||||||
res.val.i = (int) what->a2.p;
|
res.val.i = (int) what->a2.p;
|
||||||
break;
|
break;
|
||||||
|
case 'C':
|
||||||
|
res = * ((struct f_val *) what->a1.p);
|
||||||
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
res.type = what->a1.i;
|
res.type = what->a1.i;
|
||||||
res.val.i = * ((int *) what->a2.p);
|
res.val.i = * ((int *) what->a2.p);
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
ONEARG;
|
ONEARG;
|
||||||
switch (v1.type) {
|
val_print(v1);
|
||||||
case T_VOID: printf( "(void)" ); break;
|
|
||||||
case T_INT: printf( "%d ", v1.val.i ); break;
|
|
||||||
case T_STRING: printf( "%s", v1.val.i ); break;
|
|
||||||
default: runtime( "Print of variable of unknown type" );
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case '?': /* ? has really strange error value, so we can implement if ... else nicely :-) */
|
case '?': /* ? has really strange error value, so we can implement if ... else nicely :-) */
|
||||||
ONEARG;
|
ONEARG;
|
||||||
|
|
|
@ -40,6 +40,7 @@ struct f_val {
|
||||||
ip_addr ip;
|
ip_addr ip;
|
||||||
struct prefix px;
|
struct prefix px;
|
||||||
char *s;
|
char *s;
|
||||||
|
struct f_tree *t;
|
||||||
} val;
|
} val;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,10 +51,16 @@ struct filter {
|
||||||
|
|
||||||
void filters_postconfig(void);
|
void filters_postconfig(void);
|
||||||
struct f_inst *f_new_inst(void);
|
struct f_inst *f_new_inst(void);
|
||||||
|
struct f_tree *f_new_tree(void);
|
||||||
|
|
||||||
|
struct f_tree *build_tree(struct f_tree *);
|
||||||
|
struct f_tree *find_tree(struct f_tree *t, struct f_val val);
|
||||||
|
|
||||||
int f_run(struct filter *filter, struct rte **rte, struct ea_list **tmp_attrs, struct linpool *tmp_pool);
|
int f_run(struct filter *filter, struct rte **rte, struct ea_list **tmp_attrs, struct linpool *tmp_pool);
|
||||||
char *filter_name(struct filter *filter);
|
char *filter_name(struct filter *filter);
|
||||||
|
|
||||||
|
int val_compare(struct f_val v1, struct f_val v2);
|
||||||
|
void val_print(struct f_val v);
|
||||||
|
|
||||||
#define F_NOP 0
|
#define F_NOP 0
|
||||||
#define F_ACCEPT 1 /* Need to preserve ordering: accepts < rejects! */
|
#define F_ACCEPT 1 /* Need to preserve ordering: accepts < rejects! */
|
||||||
|
@ -85,4 +92,10 @@ char *filter_name(struct filter *filter);
|
||||||
|
|
||||||
#define T_SET 0x80
|
#define T_SET 0x80
|
||||||
|
|
||||||
|
struct f_tree {
|
||||||
|
struct f_tree *left, *right;
|
||||||
|
struct f_val from, to;
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
125
filter/tree.c
Normal file
125
filter/tree.c
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Filters: utility functions
|
||||||
|
*
|
||||||
|
* Copyright 1998 Pavel Machek <pavel@ucw.cz>
|
||||||
|
*
|
||||||
|
* Can be freely distributed and used under the terms of the GNU GPL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/signal.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
|
||||||
|
#include "nest/bird.h"
|
||||||
|
#include "lib/lists.h"
|
||||||
|
#include "lib/resource.h"
|
||||||
|
#include "lib/socket.h"
|
||||||
|
#include "nest/route.h"
|
||||||
|
#include "nest/protocol.h"
|
||||||
|
#include "nest/iface.h"
|
||||||
|
#include "conf/conf.h"
|
||||||
|
#include "filter/filter.h"
|
||||||
|
|
||||||
|
/* Finds n-th item in list linked by right. Trashes pointers in right. */
|
||||||
|
static struct f_tree *
|
||||||
|
find_nth(struct f_tree *from, int nth)
|
||||||
|
{
|
||||||
|
struct f_tree *pivot;
|
||||||
|
int lcount = 0, rcount = 0;
|
||||||
|
struct f_tree *left, *right, *next;
|
||||||
|
|
||||||
|
pivot = from;
|
||||||
|
|
||||||
|
left = right = NULL;
|
||||||
|
next = from->right;
|
||||||
|
while (from = next) {
|
||||||
|
next = from->right;
|
||||||
|
if (val_compare(pivot->from, from->from)==1) {
|
||||||
|
from->right = left;
|
||||||
|
left = from;
|
||||||
|
lcount++;
|
||||||
|
} else {
|
||||||
|
from->right = right;
|
||||||
|
right = from;
|
||||||
|
rcount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lcount == nth)
|
||||||
|
return pivot;
|
||||||
|
if (lcount < nth)
|
||||||
|
return find_nth(right, nth-lcount-1);
|
||||||
|
return find_nth(left, nth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gets list linked by left, finds its median, trashes pointers in right */
|
||||||
|
static struct f_tree *
|
||||||
|
find_median(struct f_tree *from)
|
||||||
|
{
|
||||||
|
struct f_tree *t = from;
|
||||||
|
int cnt = 0;
|
||||||
|
|
||||||
|
if (!from)
|
||||||
|
return NULL;
|
||||||
|
do {
|
||||||
|
t->right = t->left;
|
||||||
|
cnt++;
|
||||||
|
} while (t = t->left);
|
||||||
|
return find_nth(from, cnt/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct f_tree *
|
||||||
|
find_tree(struct f_tree *t, struct f_val val)
|
||||||
|
{
|
||||||
|
if (!t)
|
||||||
|
return 0;
|
||||||
|
if ((val_compare(t->from, val) != 1) &&
|
||||||
|
(val_compare(t->to, val) != -1))
|
||||||
|
return t;
|
||||||
|
if (val_compare(t->from, val) == -1)
|
||||||
|
return find_tree(t->right, val);
|
||||||
|
else
|
||||||
|
return find_tree(t->left, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gets list linked by left */
|
||||||
|
struct f_tree *
|
||||||
|
build_tree(struct f_tree *from)
|
||||||
|
{
|
||||||
|
struct f_tree *median, *t = from, *next, *left = NULL, *right = NULL;
|
||||||
|
|
||||||
|
median = find_median(from);
|
||||||
|
if (!median)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
do {
|
||||||
|
next = t->left;
|
||||||
|
if (t == median)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (val_compare(median->from, t->from)==1) {
|
||||||
|
t->left = left;
|
||||||
|
left = t;
|
||||||
|
} else {
|
||||||
|
t->left = right;
|
||||||
|
right = t;
|
||||||
|
}
|
||||||
|
} while(t = next);
|
||||||
|
|
||||||
|
median->left = build_tree(left);
|
||||||
|
median->right = build_tree(right);
|
||||||
|
return median;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct f_tree *
|
||||||
|
f_new_tree(void)
|
||||||
|
{
|
||||||
|
struct f_tree * ret;
|
||||||
|
ret = cfg_alloc(sizeof(struct f_tree));
|
||||||
|
ret->left = ret->right = NULL;
|
||||||
|
ret->from.type = ret->to.type = T_VOID;
|
||||||
|
ret->from.val.i = ret->to.val.i = 0;
|
||||||
|
ret->data = NULL;
|
||||||
|
return ret;
|
||||||
|
}
|
Loading…
Reference in a new issue