Filter: Implement mixed declarations of local variables

Allow variable declarations mixed with code, also in nested blocks with
proper scoping, and with variable initializers. E.g:

function fn(int a)
{
  int b;
  int c = 10;

  if a > 20 then
  {
    b = 30;
    int d = c * 2;
    print a, b, c, d;
  }

  string s = "Hello";
}
This commit is contained in:
Ondrej Zajicek (work) 2022-03-10 01:02:45 +01:00 committed by Ondrej Zajicek
parent a2527ee53d
commit 1ac8e11bba
4 changed files with 116 additions and 37 deletions

View file

@ -1260,8 +1260,8 @@ this:
<code> <code>
filter not_too_far filter not_too_far
int var;
{ {
int var;
if defined( rip_metric ) then if defined( rip_metric ) then
var = rip_metric; var = rip_metric;
else { else {
@ -1290,9 +1290,9 @@ local variables. Recursion is not allowed. Function definitions look like this:
<code> <code>
function name () function name ()
int local_variable;
{ {
local_variable = 5; int local_variable;
int another_variable = 5;
} }
function with_parameters (int parameter) function with_parameters (int parameter)
@ -1301,16 +1301,19 @@ function with_parameters (int parameter)
} }
</code> </code>
<p>Unlike in C, variables are declared after the <cf/function/ line, but before <p>Like in C programming language, variables are declared inside function body,
the first <cf/{/. You can't declare variables in nested blocks. Functions are either at the beginning, or mixed with other statements. Declarations may
called like in C: <cf>name(); with_parameters(5);</cf>. Function may return contain initialization. You can also declare variables in nested blocks, such
values using the <cf>return <m/[expr]/</cf> command. Returning a value exits variables have scope restricted to such block. There is a deprecated syntax to
from current function (this is similar to C). declare variables after the <cf/function/ line, but before the first <cf/{/.
Functions are called like in C: <cf>name(); with_parameters(5);</cf>. Function
may return values using the <cf>return <m/[expr]/</cf> command. Returning a
value exits from current function (this is similar to C).
<p>Filters are defined in a way similar to functions except they can't have <p>Filters are defined in a way similar to functions except they cannot have
explicit parameters. They get a route table entry as an implicit parameter, it explicit parameters. They get a route table entry as an implicit parameter, it
is also passed automatically to any functions called. The filter must terminate is also passed automatically to any functions called. The filter must terminate
with either <cf/accept/ or <cf/reject/ statement. If there's a runtime error in with either <cf/accept/ or <cf/reject/ statement. If there is a runtime error in
filter, the route is rejected. filter, the route is rejected.
<p>A nice trick to debug filters is to use <cf>show route filter <m/name/</cf> <p>A nice trick to debug filters is to use <cf>show route filter <m/name/</cf>

View file

@ -22,6 +22,36 @@ static inline u32 pair_b(u32 p) { return p & 0xFFFF; }
#define f_generate_complex(fi_code, da, arg) \ #define f_generate_complex(fi_code, da, arg) \
f_new_inst(FI_EA_SET, f_new_inst(fi_code, f_new_inst(FI_EA_GET, da), arg), da) f_new_inst(FI_EA_SET, f_new_inst(fi_code, f_new_inst(FI_EA_GET, da), arg), da)
static int
f_new_var(struct sym_scope *s)
{
/*
* - A variable is an offset on vstack from vbase.
* - Vbase is set on filter start / function call.
* - Scopes contain anonymous scopes (blocks) inside filter/function scope
* - Each scope knows number of vars in that scope
* - Offset is therefore a sum of 'slots' up to named scope
* - New variables are added on top of vstk, so intermediate values cannot
* be there during FI_VAR_INIT. I.e. no 'var' inside 'term'.
* - Also, each f_line must always have its scope, otherwise a variable may
* be defined but not initialized if relevant f_line is not executed.
*/
int offset = s->slots++;
while (!s->name)
{
s = s->next;
ASSERT(s);
offset += s->slots;
}
if (offset >= 0xff)
cf_error("Too many variables, at most 255 allowed");
return offset;
}
/* /*
* Sets and their items are during parsing handled as lists, linked * Sets and their items are during parsing handled as lists, linked
* through left ptr. The first item in a list also contains a pointer * through left ptr. The first item in a list also contains a pointer
@ -296,7 +326,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
%nonassoc ELSE %nonassoc ELSE
%type <xp> cmds_int cmd_prep %type <xp> cmds_int cmd_prep
%type <x> term cmd cmds constant constructor print_list var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail %type <x> term cmd cmd_var cmds cmds_scoped constant constructor print_list var var_init var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail
%type <fda> dynamic_attr %type <fda> dynamic_attr
%type <fsa> static_attr %type <fsa> static_attr
%type <f> filter where_filter %type <f> filter where_filter
@ -425,7 +455,7 @@ function_args:
function_vars: function_vars:
/* EMPTY */ { $$ = 0; } /* EMPTY */ { $$ = 0; }
| function_vars type symbol ';' { | function_vars type symbol ';' {
cf_define_symbol($3, SYM_VARIABLE | $2, offset, sym_->scope->slots++); cf_define_symbol($3, SYM_VARIABLE | $2, offset, f_new_var(sym_->scope));
$$ = $1 + 1; $$ = $1 + 1;
} }
; ;
@ -492,7 +522,11 @@ cmds: /* EMPTY */ { $$ = NULL; }
| cmds_int { $$ = $1.begin; } | cmds_int { $$ = $1.begin; }
; ;
cmd_prep: cmd { cmds_scoped: { cf_push_soft_scope(); } cmds { cf_pop_soft_scope(); $$ = $2; } ;
cmd_var: var | cmd ;
cmd_prep: cmd_var {
$$.begin = $$.end = $1; $$.begin = $$.end = $1;
if ($1) if ($1)
while ($$.end->next) while ($$.end->next)
@ -639,7 +673,7 @@ fprefix_set:
; ;
switch_body: /* EMPTY */ { $$ = NULL; } switch_body: /* EMPTY */ { $$ = NULL; }
| switch_body switch_items ':' cmds { | switch_body switch_items ':' cmds_scoped {
/* Fill data fields */ /* Fill data fields */
struct f_tree *t; struct f_tree *t;
struct f_line *line = f_linearize($4, 0); struct f_line *line = f_linearize($4, 0);
@ -647,7 +681,7 @@ switch_body: /* EMPTY */ { $$ = NULL; }
t->data = line; t->data = line;
$$ = f_merge_items($1, $2); $$ = f_merge_items($1, $2);
} }
| switch_body ELSECOL cmds { | switch_body ELSECOL cmds_scoped {
struct f_tree *t = f_new_tree(); struct f_tree *t = f_new_tree();
t->from.type = t->to.type = T_VOID; t->from.type = t->to.type = T_VOID;
t->right = t; t->right = t;
@ -854,8 +888,19 @@ print_list: /* EMPTY */ { $$ = NULL; }
} }
; ;
var_init:
/* empty */ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { }); }
| '=' term { $$ = $2; }
;
var:
type symbol var_init ';' {
struct symbol *sym = cf_define_symbol($2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope));
$$ = f_new_inst(FI_VAR_INIT, $3, sym);
}
cmd: cmd:
'{' cmds '}' { '{' cmds_scoped '}' {
$$ = $2; $$ = $2;
} }
| IF term THEN cmd { | IF term THEN cmd {

View file

@ -501,6 +501,18 @@
RESULT(T_BOOL, i, ipa_is_ip4(v1.val.ip)); RESULT(T_BOOL, i, ipa_is_ip4(v1.val.ip));
} }
INST(FI_VAR_INIT, 1, 0) {
NEVER_CONSTANT;
ARG_ANY(1);
SYMBOL;
ARG_TYPE(1, sym->class & 0xff);
/* New variable is always the last on stack */
uint pos = curline.vbase + sym->offset;
fstk->vstk[pos] = v1;
fstk->vcnt = pos + 1;
}
/* Set to indirect value prepared in v1 */ /* Set to indirect value prepared in v1 */
INST(FI_VAR_SET, 1, 0) { INST(FI_VAR_SET, 1, 0) {
NEVER_CONSTANT; NEVER_CONSTANT;

View file

@ -44,9 +44,8 @@ bt_test_same(onef, twof, 0);
*/ */
function t_bool() function t_bool()
bool b;
{ {
b = true; bool b = true;
bt_assert(b); bt_assert(b);
bt_assert(!!b); bt_assert(!!b);
@ -82,12 +81,11 @@ define xyzzy = (120+10);
define '1a-a1' = (xyzzy-100); define '1a-a1' = (xyzzy-100);
function t_int() function t_int()
int i;
{ {
bt_assert(xyzzy = 130); bt_assert(xyzzy = 130);
bt_assert('1a-a1' = 30); bt_assert('1a-a1' = 30);
i = four; int i = four;
i = 12*100 + 60/2 + i; i = 12*100 + 60/2 + i;
i = (i + 0); i = (i + 0);
bt_assert(i = 1234); bt_assert(i = 1234);
@ -128,9 +126,8 @@ define is2 = [(17+2), 17, 15, 11, 8, 5, 3, 2];
define is3 = [5, 17, 2, 11, 8, 15, 3, 19]; define is3 = [5, 17, 2, 11, 8, 15, 3, 19];
function t_int_set() function t_int_set()
int set is;
{ {
is = []; int set is = [];
bt_assert(is = []); bt_assert(is = []);
bt_assert(0 !~ is); bt_assert(0 !~ is);
@ -190,9 +187,8 @@ bt_test_suite(t_int_set, "Testing sets of integers");
*/ */
function t_string() function t_string()
string st;
{ {
st = "Hello"; string st = "Hello";
bt_assert(format(st) = "Hello"); bt_assert(format(st) = "Hello");
bt_assert(st ~ "Hell*"); bt_assert(st ~ "Hell*");
bt_assert(st ~ "?ello"); bt_assert(st ~ "?ello");
@ -217,9 +213,8 @@ function 'mkpair-a'(int a)
} }
function t_pair() function t_pair()
pair pp;
{ {
pp = (1, 2); pair pp = (1, 2);
bt_assert(format(pp) = "(1,2)"); bt_assert(format(pp) = "(1,2)");
bt_assert((1,2) = pp); bt_assert((1,2) = pp);
bt_assert((1,1+1) = pp); bt_assert((1,1+1) = pp);
@ -240,11 +235,9 @@ bt_test_suite(t_pair, "Testing pairs");
*/ */
function t_pair_set() function t_pair_set()
pair pp;
pair set ps;
{ {
pp = (1, 2); pair pp = (1, 2);
ps = []; pair set ps = [];
bt_assert(pp !~ ps); bt_assert(pp !~ ps);
ps = [(1,(one+one)), (3,4)..(4,8), (5,*), (6,3..6)]; ps = [(1,(one+one)), (3,4)..(4,8), (5,*), (6,3..6)];
@ -1290,6 +1283,34 @@ function fifteen()
return 15; return 15;
} }
function local_vars(int j)
{
int k = 10;
bt_assert(j = 5 && k = 10);
{
int j = 15;
k = 20;
bt_assert(j = 15 && k = 20);
}
bt_assert(j = 5 && k = 20);
if j < 10 then
{
int j = 25;
string k = "hello";
bt_assert(j = 25 && k = "hello");
}
bt_assert(j = 5 && k = 20);
int m = 100;
{
j = 35;
int k = 40;
bt_assert(j = 35 && k = 40 && m = 100);
}
bt_assert(j = 35 && k = 20 && m = 100);
}
function factorial(int x) function factorial(int x)
{ {
if x = 0 then return 0; if x = 0 then return 0;
@ -1312,21 +1333,18 @@ function hanoi_init(int a; int b)
} }
function hanoi_solve(int n; bgppath h_src; bgppath h_dst; bgppath h_aux; bool x; bool y) function hanoi_solve(int n; bgppath h_src; bgppath h_dst; bgppath h_aux; bool x; bool y)
bgppath tmp1;
bgppath tmp2;
int v;
{ {
# x -> return src or dst # x -> return src or dst
# y -> print state # y -> print state
if n = 0 then { if x then return h_src; else return h_dst; } if n = 0 then { if x then return h_src; else return h_dst; }
tmp1 = hanoi_solve(n - 1, h_src, h_aux, h_dst, true, y); bgppath tmp1 = hanoi_solve(n - 1, h_src, h_aux, h_dst, true, y);
tmp2 = hanoi_solve(n - 1, h_src, h_aux, h_dst, false, false); bgppath tmp2 = hanoi_solve(n - 1, h_src, h_aux, h_dst, false, false);
h_src = tmp1; h_src = tmp1;
h_aux = tmp2; h_aux = tmp2;
v = h_src.first; int v = h_src.first;
# bt_assert(h_dst = +empty+ || v < h_dst.first); # bt_assert(h_dst = +empty+ || v < h_dst.first);
h_src = delete(h_src, v); h_src = delete(h_src, v);
h_dst = prepend(h_dst, v); h_dst = prepend(h_dst, v);
@ -1355,6 +1373,7 @@ bgppath h_src;
bt_assert(callme(4, 4) = 16); bt_assert(callme(4, 4) = 16);
bt_assert(callme(7, 2) = 14); bt_assert(callme(7, 2) = 14);
bt_assert(callmeagain(1, 2, 3) = 6); bt_assert(callmeagain(1, 2, 3) = 6);
local_vars(5);
bt_assert(factorial(5) = 120); bt_assert(factorial(5) = 120);
bt_assert(factorial(10) = 3628800); bt_assert(factorial(10) = 3628800);