Allow default function/hook/event parameters. Addresses #972.

And changed the endianness parameter of bytestring_to_count() BIF to
default to false (big endian), mostly just to prove that the BIF parser
doesn't choke on default parameters.
This commit is contained in:
Jon Siwek 2013-05-07 14:32:22 -05:00
parent 69c7363147
commit e2a1d4a233
14 changed files with 239 additions and 28 deletions

View file

@ -459,6 +459,31 @@ The Bro scripting language supports the following built-in types.
print greeting("Dave");
Function parameters may specify default values as long as they appear
last in the parameter list:
.. code:: bro
global foo: function(s: string, t: string &default="abc", u: count &default=0);
If a function was previously declared with default parameters, the
default expressions can be omitted when implementing the function
body and they will still be used for function calls that lack those
arguments.
.. code:: bro
function foo(s: string, t: string, u: count)
{
print s, t, u;
}
And calls to the function may omit the defaults from the argument list:
.. code:: bro
foo("test");
.. bro:type:: event
Event handlers are nearly identical in both syntax and semantics to
@ -597,10 +622,10 @@ scripting language supports the following built-in attributes.
.. bro:attr:: &default
Uses a default value for a record field or container elements. For
example, ``table[int] of string &default="foo" }`` would create a
table that returns the :bro:type:`string` ``"foo"`` for any
non-existing index.
Uses a default value for a record field, a function/hook/event
parameter, or container elements. For example, ``table[int] of
string &default="foo" }`` would create a table that returns the
:bro:type:`string` ``"foo"`` for any non-existing index.
.. bro:attr:: &redef

View file

@ -5478,6 +5478,50 @@ int check_and_promote_exprs(ListExpr*& elements, TypeList* types)
return 1;
}
int check_and_promote_args(ListExpr*& args, RecordType* types)
{
expr_list& el = args->Exprs();
int ntypes = types->NumFields();
// give variadic BIFs automatic pass
if ( ntypes == 1 && types->FieldDecl(0)->type->Tag() == TYPE_ANY )
return 1;
if ( el.length() < ntypes )
{
expr_list def_elements;
// Start from rightmost parameter, work backward to fill in missing
// arguments using &default expressions.
for ( int i = ntypes - 1; i >= el.length(); --i )
{
TypeDecl* td = types->FieldDecl(i);
Attr* def_attr = td->attrs ? td->attrs->FindAttr(ATTR_DEFAULT) : 0;
if ( ! def_attr )
{
types->Error("parameter mismatch", args);
return 0;
}
def_elements.insert(def_attr->AttrExpr());
}
loop_over_list(def_elements, i)
el.append(def_elements[i]->Ref());
}
TypeList* tl = new TypeList();
for ( int i = 0; i < types->NumFields(); ++i )
tl->Append(types->FieldType(i)->Ref());
int rval = check_and_promote_exprs(args, tl);
Unref(tl);
return rval;
}
int check_and_promote_exprs_to_type(ListExpr*& elements, BroType* type)
{
expr_list& el = elements->Exprs();

View file

@ -1082,13 +1082,14 @@ Expr* get_assign_expr(Expr* op1, Expr* op2, int is_init);
// match, promote it as necessary (modifying the ref parameter accordingly)
// and return 1.
//
// The second and third forms are for promoting a list of
// The second, third, and fourth forms are for promoting a list of
// expressions (which is updated in place) to either match a list of
// types or a single type.
//
// Note, the type is not "const" because it can be ref'd.
extern int check_and_promote_expr(Expr*& e, BroType* t);
extern int check_and_promote_exprs(ListExpr*& elements, TypeList* types);
extern int check_and_promote_args(ListExpr*& args, RecordType* types);
extern int check_and_promote_exprs_to_type(ListExpr*& elements, BroType* type);
// Returns a fully simplified form of the expression. Note that passed

View file

@ -671,9 +671,25 @@ FuncType::FuncType(RecordType* arg_args, BroType* arg_yield, function_flavor arg
arg_types = new TypeList();
bool has_default_arg = false;
for ( int i = 0; i < args->NumFields(); ++i )
{
const TypeDecl* td = args->FieldDecl(i);
if ( td->attrs && td->attrs->FindAttr(ATTR_DEFAULT) )
has_default_arg = true;
else if ( has_default_arg )
{
const char* err_str = fmt("required parameter '%s' must precede "
"default parameters", td->id);
args->Error(err_str);
}
arg_types->Append(args->FieldType(i)->Ref());
}
}
string FuncType::FlavorString() const
{
@ -708,7 +724,7 @@ BroType* FuncType::YieldType()
int FuncType::MatchesIndex(ListExpr*& index) const
{
return check_and_promote_exprs(index, arg_types) ?
return check_and_promote_args(index, args) ?
MATCHES_INDEX_SCALAR : DOES_NOT_MATCH_INDEX;
}

View file

@ -318,6 +318,29 @@ void add_type(ID* id, BroType* t, attr_list* attr, int /* is_event */)
id->SetAttrs(new Attributes(attr, tnew, false));
}
static void transfer_arg_defaults(RecordType* args, RecordType* recv)
{
for ( int i = 0; i < args->NumFields(); ++i )
{
TypeDecl* args_i = args->FieldDecl(i);
TypeDecl* recv_i = recv->FieldDecl(i);
Attr* def = args_i->attrs ? args_i->attrs->FindAttr(ATTR_DEFAULT) : 0;
if ( ! def ) continue;
if ( ! recv_i->attrs )
{
attr_list* a = new attr_list();
a->append(def);
recv_i->attrs = new Attributes(a, recv_i->type, true);
}
else if ( ! recv_i->attrs->FindAttr(ATTR_DEFAULT) )
recv_i->attrs->AddAttr(def);
}
}
void begin_func(ID* id, const char* module_name, function_flavor flavor,
int is_redef, FuncType* t)
{
@ -335,6 +358,11 @@ void begin_func(ID* id, const char* module_name, function_flavor flavor,
{
if ( ! same_type(id->Type(), t) )
id->Type()->Error("incompatible types", t);
// If a previous declaration of the function had &default params,
// automatically transfer any that are missing (convenience so that
// implementations don't need to specify the &default expression again).
transfer_arg_defaults(id->Type()->AsFuncType()->Args(), t->Args());
}
else if ( is_redef )

View file

@ -30,11 +30,13 @@ BuiltinFuncArg::BuiltinFuncArg(const char* arg_name, int arg_type)
type_str = "";
}
BuiltinFuncArg::BuiltinFuncArg(const char* arg_name, const char* arg_type_str)
BuiltinFuncArg::BuiltinFuncArg(const char* arg_name, const char* arg_type_str,
const char* arg_attr_str)
{
name = arg_name;
type = TYPE_OTHER;
type_str = arg_type_str;
attr_str = arg_attr_str;
for ( int i = 0; builtin_func_arg_type[i].bif_type[0] != '\0'; ++i )
if ( ! strcmp(builtin_func_arg_type[i].bif_type, arg_type_str) )
@ -46,7 +48,8 @@ BuiltinFuncArg::BuiltinFuncArg(const char* arg_name, const char* arg_type_str)
void BuiltinFuncArg::PrintBro(FILE* fp)
{
fprintf(fp, "%s: %s%s", name, builtin_func_arg_type[type].bro_type, type_str);
fprintf(fp, "%s: %s%s %s", name, builtin_func_arg_type[type].bro_type,
type_str, attr_str);
}
void BuiltinFuncArg::PrintCDef(FILE* fp, int n)

View file

@ -25,7 +25,10 @@ extern const char* builtin_func_arg_type_bro_name[];
class BuiltinFuncArg {
public:
BuiltinFuncArg(const char* arg_name, int arg_type);
BuiltinFuncArg(const char* arg_name, const char* arg_type_str);
BuiltinFuncArg(const char* arg_name, const char* arg_type_str,
const char* arg_attr_str = "");
void SetAttrStr(const char* arg_attr_str) { attr_str = arg_attr_str; };
const char* Name() const { return name; }
int Type() const { return type; }
@ -39,6 +42,7 @@ protected:
const char* name;
int type;
const char* type_str;
const char* attr_str;
};
#endif

View file

@ -2472,7 +2472,7 @@ function bytestring_to_double%(s: string%): double
##
## Returns: The value contained in *s*, or 0 if the conversion failed.
##
function bytestring_to_count%(s: string, is_le: bool%): count
function bytestring_to_count%(s: string, is_le: bool &default=F%): count
%{
#ifdef HOST_BIGENDIAN
static const bool host_bigendian = true;

View file

@ -26,6 +26,7 @@ int check_c_mode(int t)
%}
WS [ \t]+
OWS [ \t]*
/* Note, bifcl only accepts a single "::" in IDs while the policy
layer acceptes multiple. (But the policy layer doesn't have
a hierachy. */
@ -101,7 +102,13 @@ HEX [0-9a-fA-F]+
return TOK_ID;
}
&{ID} {
/*
Hacky way to pass along arbitrary attribute expressions since the BIF parser
has little understanding of valid Bro expressions. With this pattern, the
attribute expression should stop when it reaches another attribute, another
function argument, or the end of the function declaration.
*/
&{ID}({OWS}={OWS}[^&%;,]+)? {
int t = check_c_mode(TOK_ATTR);
if ( t == TOK_ATTR )

View file

@ -277,7 +277,7 @@ void print_event_c_body(FILE *fp)
%left ',' ':'
%type <str> TOK_C_TOKEN TOK_ID TOK_CSTR TOK_WS TOK_COMMENT TOK_ATTR TOK_INT opt_ws type
%type <str> TOK_C_TOKEN TOK_ID TOK_CSTR TOK_WS TOK_COMMENT TOK_ATTR TOK_INT opt_ws type attr_list opt_attr_list
%type <val> TOK_ATOM TOK_BOOL
%union {
@ -375,7 +375,8 @@ type_def_types: TOK_RECORD
{ set_definition_type(TYPE_DEF, "Table"); }
;
event_def: event_prefix opt_ws plain_head opt_attr end_of_head ';'
event_def: event_prefix opt_ws plain_head opt_attr_list
{ fprintf(fp_bro_init, "%s", $4); } end_of_head ';'
{
print_event_c_prototype(fp_func_h, true);
print_event_c_prototype(fp_func_def, false);
@ -458,20 +459,17 @@ const_def: TOK_CONST opt_ws TOK_ID opt_ws ':' opt_ws TOK_ID opt_ws ';'
accessor);
}
/* Currently support only boolean and string values */
opt_attr_init: /* nothing */
| '=' opt_ws TOK_BOOL opt_ws
{
fprintf(fp_bro_init, "=%s%c%s", $2, ($3) ? 'T' : 'F', $4);
}
| '=' opt_ws TOK_CSTR opt_ws
{ fprintf(fp_bro_init, "=%s%s%s", $2, $3, $4); }
attr_list:
attr_list TOK_ATTR
{ $$ = concat($1, $2); }
|
TOK_ATTR
;
opt_attr: /* nothing */
| opt_attr TOK_ATTR { fprintf(fp_bro_init, "%s", $2); }
opt_ws opt_attr_init
opt_attr_list:
attr_list
| /* nothing */
{ $$ = ""; }
;
func_prefix: TOK_FUNCTION
@ -579,9 +577,10 @@ args: args_1
{ /* empty, to avoid yacc complaint about type clash */ }
;
args_1: args_1 ',' opt_ws arg opt_ws
| opt_ws arg opt_ws
{ /* empty */ }
args_1: args_1 ',' opt_ws arg opt_ws opt_attr_list
{ if ( ! args.empty() ) args[args.size()-1]->SetAttrStr($6); }
| opt_ws arg opt_ws opt_attr_list
{ if ( ! args.empty() ) args[args.size()-1]->SetAttrStr($4); }
;
// TODO: Migrate all other compound types to this rule. Once the BiF language

View file

@ -34,3 +34,4 @@
65535
0
0
65535

View file

@ -0,0 +1,15 @@
foo_func, test
foo_func, hello
bar_func, hmm, hi, 5
bar_func, cool, beans, 5
bar_func, cool, beans, 13
foo_hook, test
foo_hook, hello
bar_hook, hmm, hi, 5
bar_hook, cool, beans, 5
bar_hook, cool, beans, 13
foo_event, test
foo_event, hello
bar_event, hmm, hi, 5
bar_event, cool, beans, 5
bar_event, cool, beans, 13

View file

@ -52,4 +52,7 @@ event bro_init()
print bytestring_to_count("\x00\x00\x00\x00\x00\x00\x00\x00", T); # 0
print bytestring_to_count("\x00\x00\x00\x00\x00\x00\x00\x00", F); # 0
# test the default endianness parameter
print bytestring_to_count("\x00\x00\x00\x00\x00\x00\xff\xff"); # 65535
}

View file

@ -0,0 +1,65 @@
# @TEST-EXEC: bro -b %INPUT >out
# @TEST-EXEC: btest-diff out
### functions
global foo_func: function(a: string &default="hello");
# &defaults transfer from the declaration automatically
function foo_func(a: string)
{
print "foo_func", a;
}
function bar_func(a: string, b: string &default="hi", c: count &default=5)
{
print "bar_func", a, b, c;
}
### events
global foo_event: event(a: string &default="hello");
event foo_event(a: string)
{
print "foo_event", a;
}
event bar_event(a: string, b: string &default="hi", c: count &default=5)
{
print "bar_event", a, b, c;
}
### hooks
global foo_hook: hook(a: string &default="hello");
hook foo_hook(a: string)
{
print "foo_hook", a;
}
hook bar_hook(a: string, b: string &default="hi", c: count &default=5)
{
print "bar_hook", a, b, c;
}
{}
foo_func("test");
foo_func();
bar_func("hmm");
bar_func("cool", "beans");
bar_func("cool", "beans", 13);
event foo_event("test");
event foo_event();
event bar_event("hmm");
event bar_event("cool", "beans");
event bar_event("cool", "beans", 13);
hook foo_hook("test");
hook foo_hook();
hook bar_hook("hmm");
hook bar_hook("cool", "beans");
hook bar_hook("cool", "beans", 13);