mirror of
https://github.com/zeek/zeek.git
synced 2025-10-05 16:18:19 +00:00
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:
parent
69c7363147
commit
e2a1d4a233
14 changed files with 239 additions and 28 deletions
|
@ -459,6 +459,31 @@ The Bro scripting language supports the following built-in types.
|
||||||
|
|
||||||
print greeting("Dave");
|
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
|
.. bro:type:: event
|
||||||
|
|
||||||
Event handlers are nearly identical in both syntax and semantics to
|
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
|
.. bro:attr:: &default
|
||||||
|
|
||||||
Uses a default value for a record field or container elements. For
|
Uses a default value for a record field, a function/hook/event
|
||||||
example, ``table[int] of string &default="foo" }`` would create a
|
parameter, or container elements. For example, ``table[int] of
|
||||||
table that returns the :bro:type:`string` ``"foo"`` for any
|
string &default="foo" }`` would create a table that returns the
|
||||||
non-existing index.
|
:bro:type:`string` ``"foo"`` for any non-existing index.
|
||||||
|
|
||||||
.. bro:attr:: &redef
|
.. bro:attr:: &redef
|
||||||
|
|
||||||
|
|
44
src/Expr.cc
44
src/Expr.cc
|
@ -5478,6 +5478,50 @@ int check_and_promote_exprs(ListExpr*& elements, TypeList* types)
|
||||||
return 1;
|
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)
|
int check_and_promote_exprs_to_type(ListExpr*& elements, BroType* type)
|
||||||
{
|
{
|
||||||
expr_list& el = elements->Exprs();
|
expr_list& el = elements->Exprs();
|
||||||
|
|
|
@ -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)
|
// match, promote it as necessary (modifying the ref parameter accordingly)
|
||||||
// and return 1.
|
// 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
|
// expressions (which is updated in place) to either match a list of
|
||||||
// types or a single type.
|
// types or a single type.
|
||||||
//
|
//
|
||||||
// Note, the type is not "const" because it can be ref'd.
|
// 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_expr(Expr*& e, BroType* t);
|
||||||
extern int check_and_promote_exprs(ListExpr*& elements, TypeList* types);
|
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);
|
extern int check_and_promote_exprs_to_type(ListExpr*& elements, BroType* type);
|
||||||
|
|
||||||
// Returns a fully simplified form of the expression. Note that passed
|
// Returns a fully simplified form of the expression. Note that passed
|
||||||
|
|
18
src/Type.cc
18
src/Type.cc
|
@ -671,8 +671,24 @@ FuncType::FuncType(RecordType* arg_args, BroType* arg_yield, function_flavor arg
|
||||||
|
|
||||||
arg_types = new TypeList();
|
arg_types = new TypeList();
|
||||||
|
|
||||||
|
bool has_default_arg = false;
|
||||||
|
|
||||||
for ( int i = 0; i < args->NumFields(); ++i )
|
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());
|
arg_types->Append(args->FieldType(i)->Ref());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string FuncType::FlavorString() const
|
string FuncType::FlavorString() const
|
||||||
|
@ -708,7 +724,7 @@ BroType* FuncType::YieldType()
|
||||||
|
|
||||||
int FuncType::MatchesIndex(ListExpr*& index) const
|
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;
|
MATCHES_INDEX_SCALAR : DOES_NOT_MATCH_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
28
src/Var.cc
28
src/Var.cc
|
@ -318,6 +318,29 @@ void add_type(ID* id, BroType* t, attr_list* attr, int /* is_event */)
|
||||||
id->SetAttrs(new Attributes(attr, tnew, false));
|
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,
|
void begin_func(ID* id, const char* module_name, function_flavor flavor,
|
||||||
int is_redef, FuncType* t)
|
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) )
|
if ( ! same_type(id->Type(), t) )
|
||||||
id->Type()->Error("incompatible types", 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 )
|
else if ( is_redef )
|
||||||
|
|
|
@ -30,11 +30,13 @@ BuiltinFuncArg::BuiltinFuncArg(const char* arg_name, int arg_type)
|
||||||
type_str = "";
|
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;
|
name = arg_name;
|
||||||
type = TYPE_OTHER;
|
type = TYPE_OTHER;
|
||||||
type_str = arg_type_str;
|
type_str = arg_type_str;
|
||||||
|
attr_str = arg_attr_str;
|
||||||
|
|
||||||
for ( int i = 0; builtin_func_arg_type[i].bif_type[0] != '\0'; ++i )
|
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) )
|
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)
|
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)
|
void BuiltinFuncArg::PrintCDef(FILE* fp, int n)
|
||||||
|
|
|
@ -25,7 +25,10 @@ extern const char* builtin_func_arg_type_bro_name[];
|
||||||
class BuiltinFuncArg {
|
class BuiltinFuncArg {
|
||||||
public:
|
public:
|
||||||
BuiltinFuncArg(const char* arg_name, int arg_type);
|
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; }
|
const char* Name() const { return name; }
|
||||||
int Type() const { return type; }
|
int Type() const { return type; }
|
||||||
|
@ -39,6 +42,7 @@ protected:
|
||||||
const char* name;
|
const char* name;
|
||||||
int type;
|
int type;
|
||||||
const char* type_str;
|
const char* type_str;
|
||||||
|
const char* attr_str;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2472,7 +2472,7 @@ function bytestring_to_double%(s: string%): double
|
||||||
##
|
##
|
||||||
## Returns: The value contained in *s*, or 0 if the conversion failed.
|
## 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
|
#ifdef HOST_BIGENDIAN
|
||||||
static const bool host_bigendian = true;
|
static const bool host_bigendian = true;
|
||||||
|
|
|
@ -26,6 +26,7 @@ int check_c_mode(int t)
|
||||||
%}
|
%}
|
||||||
|
|
||||||
WS [ \t]+
|
WS [ \t]+
|
||||||
|
OWS [ \t]*
|
||||||
/* Note, bifcl only accepts a single "::" in IDs while the policy
|
/* Note, bifcl only accepts a single "::" in IDs while the policy
|
||||||
layer acceptes multiple. (But the policy layer doesn't have
|
layer acceptes multiple. (But the policy layer doesn't have
|
||||||
a hierachy. */
|
a hierachy. */
|
||||||
|
@ -101,7 +102,13 @@ HEX [0-9a-fA-F]+
|
||||||
return TOK_ID;
|
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);
|
int t = check_c_mode(TOK_ATTR);
|
||||||
|
|
||||||
if ( t == TOK_ATTR )
|
if ( t == TOK_ATTR )
|
||||||
|
|
|
@ -277,7 +277,7 @@ void print_event_c_body(FILE *fp)
|
||||||
|
|
||||||
%left ',' ':'
|
%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
|
%type <val> TOK_ATOM TOK_BOOL
|
||||||
|
|
||||||
%union {
|
%union {
|
||||||
|
@ -375,7 +375,8 @@ type_def_types: TOK_RECORD
|
||||||
{ set_definition_type(TYPE_DEF, "Table"); }
|
{ 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_h, true);
|
||||||
print_event_c_prototype(fp_func_def, false);
|
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);
|
accessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attr_list:
|
||||||
/* Currently support only boolean and string values */
|
attr_list TOK_ATTR
|
||||||
opt_attr_init: /* nothing */
|
{ $$ = concat($1, $2); }
|
||||||
| '=' opt_ws TOK_BOOL opt_ws
|
|
|
||||||
{
|
TOK_ATTR
|
||||||
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); }
|
|
||||||
;
|
;
|
||||||
|
|
||||||
opt_attr: /* nothing */
|
opt_attr_list:
|
||||||
| opt_attr TOK_ATTR { fprintf(fp_bro_init, "%s", $2); }
|
attr_list
|
||||||
opt_ws opt_attr_init
|
| /* nothing */
|
||||||
|
{ $$ = ""; }
|
||||||
;
|
;
|
||||||
|
|
||||||
func_prefix: TOK_FUNCTION
|
func_prefix: TOK_FUNCTION
|
||||||
|
@ -579,9 +577,10 @@ args: args_1
|
||||||
{ /* empty, to avoid yacc complaint about type clash */ }
|
{ /* empty, to avoid yacc complaint about type clash */ }
|
||||||
;
|
;
|
||||||
|
|
||||||
args_1: args_1 ',' opt_ws arg opt_ws
|
args_1: args_1 ',' opt_ws arg opt_ws opt_attr_list
|
||||||
| opt_ws arg opt_ws
|
{ if ( ! args.empty() ) args[args.size()-1]->SetAttrStr($6); }
|
||||||
{ /* empty */ }
|
| 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
|
// TODO: Migrate all other compound types to this rule. Once the BiF language
|
||||||
|
|
|
@ -34,3 +34,4 @@
|
||||||
65535
|
65535
|
||||||
0
|
0
|
||||||
0
|
0
|
||||||
|
65535
|
||||||
|
|
15
testing/btest/Baseline/language.default-params/out
Normal file
15
testing/btest/Baseline/language.default-params/out
Normal 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
|
|
@ -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", T); # 0
|
||||||
print bytestring_to_count("\x00\x00\x00\x00\x00\x00\x00\x00", F); # 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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
65
testing/btest/language/default-params.bro
Normal file
65
testing/btest/language/default-params.bro
Normal 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);
|
Loading…
Add table
Add a link
Reference in a new issue