diff --git a/NEWS b/NEWS index 77f899005d..665636b174 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,19 @@ New Functionality have changed their signatures to work with opaques types rather than global state as it was before. +- The scripting language now supports a constructing sets, tables, + vectors, and records by name: + + type MyRecordType: record { + c: count; + s: string &optional; + }; + + global r: MyRecordType = record($c = 7); + + type MySet: set[MyRec]; + global s = MySet([$c=1], [$c=2]); + - Strings now support the subscript operator to extract individual characters and substrings (e.g., s[4], s[1,5]). The index expression can take up to two indices for the start and end index of the diff --git a/aux/broctl b/aux/broctl index 3389de4a60..0eca32b35d 160000 --- a/aux/broctl +++ b/aux/broctl @@ -1 +1 @@ -Subproject commit 3389de4a6045451f66b6cd52074c746ec9be551e +Subproject commit 0eca32b35d16a4d387f41976ab46360ee6ecaed8 diff --git a/doc/scripts/builtins.rst b/doc/scripts/builtins.rst index 369f38c9eb..2f5c220458 100644 --- a/doc/scripts/builtins.rst +++ b/doc/scripts/builtins.rst @@ -246,6 +246,31 @@ The Bro scripting language supports the following built-in types. [5] = "five", }; + A table constructor (equivalent to above example) can also be used + to create a table: + + .. code:: bro + + global t2: table[count] of string = table( + [11] = "eleven", + [5] = "five" + ); + + Table constructors can also be explicitly named by a type, which is + useful for when a more complex index type could otherwise be + ambiguous: + + .. code:: bro + + type MyRec: record { + a: count &optional; + b: count; + }; + + type MyTable: table[MyRec] of string; + + global t3 = MyTable([[$b=5]] = "b5", [[$b=7]] = "b7"); + Accessing table elements if provided by enclosing values within square brackets (``[]``), for example: @@ -308,6 +333,28 @@ The Bro scripting language supports the following built-in types. The types are explicitly shown in the example above, but they could have been left to type inference. + A set constructor (equivalent to above example) can also be used to + create a set: + + .. code:: bro + + global s3: set[port] = set(21/tcp, 23/tcp, 80/tcp, 443/tcp); + + Set constructors can also be explicitly named by a type, which is + useful for when a more complex index type could otherwise be + ambiguous: + + .. code:: bro + + type MyRec: record { + a: count &optional; + b: count; + }; + + type MySet: set[MyRec]; + + global s4 = MySet([$b=1], [$b=2]); + Set membership is tested with ``in``: .. code:: bro @@ -349,6 +396,21 @@ The Bro scripting language supports the following built-in types. global v: vector of string = vector("one", "two", "three"); + Vector constructors can also be explicitly named by a type, which + is useful for when a more complex yield type could otherwise be + ambiguous. + + .. code:: bro + + type MyRec: record { + a: count &optional; + b: count; + }; + + type MyVec: vector of MyRec; + + global v2 = MyVec([$b=1], [$b=2], [$b=3]); + Adding an element to a vector involves accessing/assigning it: .. code:: bro @@ -402,6 +464,19 @@ The Bro scripting language supports the following built-in types. if ( r?$s ) ... + Records can also be created using a constructor syntax: + + .. code:: bro + + global r2: MyRecordType = record($c = 7); + + And the constructor can be explicitly named by type, too, which + is arguably more readable code: + + .. code:: bro + + global r3 = MyRecordType($c = 42); + .. bro:type:: opaque A data type whose actual representation/implementation is diff --git a/src/Expr.cc b/src/Expr.cc index 12d3d72304..221f6545ac 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -3320,12 +3320,20 @@ bool HasFieldExpr::DoUnserialize(UnserialInfo* info) return UNSERIALIZE(¬_used) && UNSERIALIZE_STR(&field_name, 0) && UNSERIALIZE(&field); } -RecordConstructorExpr::RecordConstructorExpr(ListExpr* constructor_list) +RecordConstructorExpr::RecordConstructorExpr(ListExpr* constructor_list, + BroType* arg_type) : UnaryExpr(EXPR_RECORD_CONSTRUCTOR, constructor_list) { if ( IsError() ) return; + if ( arg_type && arg_type->Tag() != TYPE_RECORD ) + { + Error("bad record constructor type", arg_type); + SetError(); + return; + } + // Spin through the list, which should be comprised of // either record's or record-field-assign, and build up a // record type to associate with this constructor. @@ -3365,7 +3373,17 @@ RecordConstructorExpr::RecordConstructorExpr(ListExpr* constructor_list) } } - SetType(new RecordType(record_types)); + ctor_type = new RecordType(record_types); + + if ( arg_type ) + SetType(arg_type->Ref()); + else + SetType(ctor_type->Ref()); + } + +RecordConstructorExpr::~RecordConstructorExpr() + { + Unref(ctor_type); } Val* RecordConstructorExpr::InitVal(const BroType* t, Val* aggr) const @@ -3391,7 +3409,7 @@ Val* RecordConstructorExpr::InitVal(const BroType* t, Val* aggr) const Val* RecordConstructorExpr::Fold(Val* v) const { ListVal* lv = v->AsListVal(); - RecordType* rt = type->AsRecordType(); + RecordType* rt = ctor_type->AsRecordType(); if ( lv->Length() != rt->NumFields() ) Internal("inconsistency evaluating record constructor"); @@ -3401,6 +3419,19 @@ Val* RecordConstructorExpr::Fold(Val* v) const for ( int i = 0; i < lv->Length(); ++i ) rv->Assign(i, lv->Index(i)->Ref()); + if ( ! same_type(rt, type) ) + { + RecordVal* new_val = rv->CoerceTo(type->AsRecordType()); + + if ( new_val ) + { + Unref(rv); + rv = new_val; + } + else + Internal("record constructor coercion failed"); + } + return rv; } @@ -3416,37 +3447,89 @@ IMPLEMENT_SERIAL(RecordConstructorExpr, SER_RECORD_CONSTRUCTOR_EXPR); bool RecordConstructorExpr::DoSerialize(SerialInfo* info) const { DO_SERIALIZE(SER_RECORD_CONSTRUCTOR_EXPR, UnaryExpr); + SERIALIZE_OPTIONAL(ctor_type); return true; } bool RecordConstructorExpr::DoUnserialize(UnserialInfo* info) { DO_UNSERIALIZE(UnaryExpr); + BroType* t = 0; + UNSERIALIZE_OPTIONAL(t, RecordType::Unserialize(info)); + ctor_type = t->AsRecordType(); return true; } TableConstructorExpr::TableConstructorExpr(ListExpr* constructor_list, - attr_list* arg_attrs) + attr_list* arg_attrs, BroType* arg_type) : UnaryExpr(EXPR_TABLE_CONSTRUCTOR, constructor_list) { if ( IsError() ) return; - if ( constructor_list->Exprs().length() == 0 ) - SetType(new TableType(new TypeList(base_type(TYPE_ANY)), 0)); + if ( arg_type ) + { + if ( ! arg_type->IsTable() ) + { + Error("bad table constructor type", arg_type); + SetError(); + return; + } + + SetType(arg_type->Ref()); + } else { - SetType(init_type(constructor_list)); + if ( constructor_list->Exprs().length() == 0 ) + SetType(new TableType(new TypeList(base_type(TYPE_ANY)), 0)); + else + { + SetType(init_type(constructor_list)); - if ( ! type ) - SetError(); + if ( ! type ) + SetError(); - else if ( type->Tag() != TYPE_TABLE || - type->AsTableType()->IsSet() ) - SetError("values in table(...) constructor do not specify a table"); + else if ( type->Tag() != TYPE_TABLE || + type->AsTableType()->IsSet() ) + SetError("values in table(...) constructor do not specify a table"); + } } attrs = arg_attrs ? new Attributes(arg_attrs, type, false) : 0; + + type_list* indices = type->AsTableType()->Indices()->Types(); + const expr_list& cle = constructor_list->Exprs(); + + // check and promote all index expressions in ctor list + loop_over_list(cle, i) + { + if ( cle[i]->Tag() != EXPR_ASSIGN ) + continue; + + Expr* idx_expr = cle[i]->AsAssignExpr()->Op1(); + + if ( idx_expr->Tag() != EXPR_LIST ) + continue; + + expr_list& idx_exprs = idx_expr->AsListExpr()->Exprs(); + + if ( idx_exprs.length() != indices->length() ) + continue; + + loop_over_list(idx_exprs, j) + { + Expr* idx = idx_exprs[j]; + + if ( check_and_promote_expr(idx, (*indices)[j]) ) + { + if ( idx != idx_exprs[j] ) + idx_exprs.replace(j, idx); + continue; + } + + ExprError("inconsistent types in table constructor"); + } + } } Val* TableConstructorExpr::Eval(Frame* f) const @@ -3502,16 +3585,30 @@ bool TableConstructorExpr::DoUnserialize(UnserialInfo* info) } SetConstructorExpr::SetConstructorExpr(ListExpr* constructor_list, - attr_list* arg_attrs) + attr_list* arg_attrs, BroType* arg_type) : UnaryExpr(EXPR_SET_CONSTRUCTOR, constructor_list) { if ( IsError() ) return; - if ( constructor_list->Exprs().length() == 0 ) - SetType(new ::SetType(new TypeList(base_type(TYPE_ANY)), 0)); + if ( arg_type ) + { + if ( ! arg_type->IsSet() ) + { + Error("bad set constructor type", arg_type); + SetError(); + return; + } + + SetType(arg_type->Ref()); + } else - SetType(init_type(constructor_list)); + { + if ( constructor_list->Exprs().length() == 0 ) + SetType(new ::SetType(new TypeList(base_type(TYPE_ANY)), 0)); + else + SetType(init_type(constructor_list)); + } if ( ! type ) SetError(); @@ -3520,6 +3617,37 @@ SetConstructorExpr::SetConstructorExpr(ListExpr* constructor_list, SetError("values in set(...) constructor do not specify a set"); attrs = arg_attrs ? new Attributes(arg_attrs, type, false) : 0; + + type_list* indices = type->AsTableType()->Indices()->Types(); + expr_list& cle = constructor_list->Exprs(); + + if ( indices->length() == 1 ) + { + if ( ! check_and_promote_exprs_to_type(constructor_list, + (*indices)[0]) ) + ExprError("inconsistent type in set constructor"); + } + + else if ( indices->length() > 1 ) + { + // Check/promote each expression in composite index. + loop_over_list(cle, i) + { + Expr* ce = cle[i]; + ListExpr* le = ce->AsListExpr(); + + if ( ce->Tag() == EXPR_LIST && + check_and_promote_exprs(le, type->AsTableType()->Indices()) ) + { + if ( le != cle[i] ) + cle.replace(i, le); + + continue; + } + + ExprError("inconsistent types in set constructor"); + } + } } Val* SetConstructorExpr::Eval(Frame* f) const @@ -3590,31 +3718,50 @@ bool SetConstructorExpr::DoUnserialize(UnserialInfo* info) return true; } -VectorConstructorExpr::VectorConstructorExpr(ListExpr* constructor_list) +VectorConstructorExpr::VectorConstructorExpr(ListExpr* constructor_list, + BroType* arg_type) : UnaryExpr(EXPR_VECTOR_CONSTRUCTOR, constructor_list) { if ( IsError() ) return; - if ( constructor_list->Exprs().length() == 0 ) + if ( arg_type ) { - // vector(). - SetType(new ::VectorType(base_type(TYPE_ANY))); - return; - } + if ( arg_type->Tag() != TYPE_VECTOR ) + { + Error("bad vector constructor type", arg_type); + SetError(); + return; + } - BroType* t = merge_type_list(constructor_list); - if ( t ) - { - SetType(new VectorType(t->Ref())); - - if ( ! check_and_promote_exprs_to_type(constructor_list, t) ) - ExprError("inconsistent types in vector constructor"); - - Unref(t); + SetType(arg_type->Ref()); } else - SetError(); + { + if ( constructor_list->Exprs().length() == 0 ) + { + // vector(). + SetType(new ::VectorType(base_type(TYPE_ANY))); + return; + } + + BroType* t = merge_type_list(constructor_list); + + if ( t ) + { + SetType(new VectorType(t->Ref())); + Unref(t); + } + else + { + SetError(); + return; + } + } + + if ( ! check_and_promote_exprs_to_type(constructor_list, + type->AsVectorType()->YieldType()) ) + ExprError("inconsistent types in vector constructor"); } Val* VectorConstructorExpr::Eval(Frame* f) const diff --git a/src/Expr.h b/src/Expr.h index bb7526d502..ba173feae0 100644 --- a/src/Expr.h +++ b/src/Expr.h @@ -57,6 +57,8 @@ extern const char* expr_name(BroExprTag t); class Stmt; class Frame; class ListExpr; +class NameExpr; +class AssignExpr; class CallExpr; class EventExpr; @@ -159,12 +161,37 @@ public: CHECK_TAG(tag, EXPR_LIST, "ExprVal::AsListExpr", expr_name) return (const ListExpr*) this; } + ListExpr* AsListExpr() { CHECK_TAG(tag, EXPR_LIST, "ExprVal::AsListExpr", expr_name) return (ListExpr*) this; } + const NameExpr* AsNameExpr() const + { + CHECK_TAG(tag, EXPR_NAME, "ExprVal::AsNameExpr", expr_name) + return (const NameExpr*) this; + } + + NameExpr* AsNameExpr() + { + CHECK_TAG(tag, EXPR_NAME, "ExprVal::AsNameExpr", expr_name) + return (NameExpr*) this; + } + + const AssignExpr* AsAssignExpr() const + { + CHECK_TAG(tag, EXPR_ASSIGN, "ExprVal::AsAssignExpr", expr_name) + return (const AssignExpr*) this; + } + + AssignExpr* AsAssignExpr() + { + CHECK_TAG(tag, EXPR_ASSIGN, "ExprVal::AsAssignExpr", expr_name) + return (AssignExpr*) this; + } + void Describe(ODesc* d) const; bool Serialize(SerialInfo* info) const; @@ -729,7 +756,8 @@ protected: class RecordConstructorExpr : public UnaryExpr { public: - RecordConstructorExpr(ListExpr* constructor_list); + RecordConstructorExpr(ListExpr* constructor_list, BroType* arg_type = 0); + ~RecordConstructorExpr(); protected: friend class Expr; @@ -741,11 +769,14 @@ protected: void ExprDescribe(ODesc* d) const; DECLARE_SERIAL(RecordConstructorExpr); + + RecordType* ctor_type; // type inferred from the ctor expression list args }; class TableConstructorExpr : public UnaryExpr { public: - TableConstructorExpr(ListExpr* constructor_list, attr_list* attrs); + TableConstructorExpr(ListExpr* constructor_list, attr_list* attrs, + BroType* arg_type = 0); ~TableConstructorExpr() { Unref(attrs); } Attributes* Attrs() { return attrs; } @@ -767,7 +798,8 @@ protected: class SetConstructorExpr : public UnaryExpr { public: - SetConstructorExpr(ListExpr* constructor_list, attr_list* attrs); + SetConstructorExpr(ListExpr* constructor_list, attr_list* attrs, + BroType* arg_type = 0); ~SetConstructorExpr() { Unref(attrs); } Attributes* Attrs() { return attrs; } @@ -789,7 +821,7 @@ protected: class VectorConstructorExpr : public UnaryExpr { public: - VectorConstructorExpr(ListExpr* constructor_list); + VectorConstructorExpr(ListExpr* constructor_list, BroType* arg_type = 0); Val* Eval(Frame* f) const; diff --git a/src/Type.h b/src/Type.h index 0b7620cd68..bad51776d9 100644 --- a/src/Type.h +++ b/src/Type.h @@ -217,6 +217,11 @@ public: return tag == TYPE_TABLE && (YieldType() == 0); } + int IsTable() const + { + return tag == TYPE_TABLE && (YieldType() != 0); + } + BroType* Ref() { ::Ref(this); return this; } virtual void Describe(ODesc* d) const; diff --git a/src/parse.y b/src/parse.y index 7ce1174595..2a79ebe231 100644 --- a/src/parse.y +++ b/src/parse.y @@ -522,10 +522,52 @@ expr: $$ = new VectorConstructorExpr($3); } - | expr '(' opt_expr_list ')' + | expr '(' { - set_location(@1, @4); - $$ = new CallExpr($1, $3, in_hook > 0); + if ( $1->Tag() == EXPR_NAME && $1->Type()->IsTable() ) + ++in_init; + } + + opt_expr_list + { + if ( $1->Tag() == EXPR_NAME && $1->Type()->IsTable() ) + --in_init; + } + + ')' + { + set_location(@1, @6); + + BroType* ctor_type = 0; + + if ( $1->Tag() == EXPR_NAME && + (ctor_type = $1->AsNameExpr()->Id()->AsType()) ) + { + switch ( ctor_type->Tag() ) { + case TYPE_RECORD: + $$ = new RecordConstructorExpr($4, ctor_type); + break; + + case TYPE_TABLE: + if ( ctor_type->IsTable() ) + $$ = new TableConstructorExpr($4, 0, ctor_type); + else + $$ = new SetConstructorExpr($4, 0, ctor_type); + + break; + + case TYPE_VECTOR: + $$ = new VectorConstructorExpr($4, ctor_type); + break; + + default: + $1->Error("constructor type not implemented"); + YYERROR; + } + } + + else + $$ = new CallExpr($1, $4, in_hook > 0); } | TOK_HOOK { ++in_hook; } expr diff --git a/testing/btest/Baseline/language.named-record-ctors/out b/testing/btest/Baseline/language.named-record-ctors/out new file mode 100644 index 0000000000..39b2ed7c0b --- /dev/null +++ b/testing/btest/Baseline/language.named-record-ctors/out @@ -0,0 +1,2 @@ +[min=, max=2] +[min=7, max=42] diff --git a/testing/btest/Baseline/language.named-set-ctors/out b/testing/btest/Baseline/language.named-set-ctors/out new file mode 100644 index 0000000000..66b0baed7f --- /dev/null +++ b/testing/btest/Baseline/language.named-set-ctors/out @@ -0,0 +1,13 @@ +{ +1, +5, +3 +} +{ +[min=, max=5], +[min=, max=2] +} +{ +[test, 1] , +[cool, 2] +} diff --git a/testing/btest/Baseline/language.named-table-ctors/out b/testing/btest/Baseline/language.named-table-ctors/out new file mode 100644 index 0000000000..23554d10f6 --- /dev/null +++ b/testing/btest/Baseline/language.named-table-ctors/out @@ -0,0 +1,19 @@ +{ +[1] = one, +[5] = five, +[3] = three +} +{ +[[min=, max=5]] = max5, +[[min=, max=2]] = max2 +} +{ +[test, 1] = test1, +[cool, 2] = cool2 +} +{ +[two] = 2.0, +[one] = 1.0, +[three] = 3.0 +} +0 diff --git a/testing/btest/Baseline/language.named-vector-ctors/out b/testing/btest/Baseline/language.named-vector-ctors/out new file mode 100644 index 0000000000..53ed260c93 --- /dev/null +++ b/testing/btest/Baseline/language.named-vector-ctors/out @@ -0,0 +1,3 @@ +[one, two, three] +[1.0, 2.0, 3.0] +[[min=, max=1], [min=, max=2], [min=, max=3]] diff --git a/testing/btest/language/named-record-ctors.bro b/testing/btest/language/named-record-ctors.bro new file mode 100644 index 0000000000..7f04b9d4b0 --- /dev/null +++ b/testing/btest/language/named-record-ctors.bro @@ -0,0 +1,12 @@ +# @TEST-EXEC: bro -b %INPUT >out +# @TEST-EXEC: btest-diff out + +type MyRec: record { + min: count &optional; + max: count; +}; + +local myrec: MyRec = MyRec($max=2); +print myrec; +myrec = MyRec($min=7, $max=42); +print myrec; diff --git a/testing/btest/language/named-set-ctors.bro b/testing/btest/language/named-set-ctors.bro new file mode 100644 index 0000000000..083937c42e --- /dev/null +++ b/testing/btest/language/named-set-ctors.bro @@ -0,0 +1,19 @@ +# @TEST-EXEC: bro -b %INPUT >out +# @TEST-EXEC: btest-diff out + +type MyRec: record { + min: count &optional; + max: count; +}; + +type FooSet: set[count]; +type FooSetRec: set[MyRec]; +type FooSetComp: set[string, count]; + +global myset: FooSet = FooSet(1, 5, 3); +global mysetrec: FooSetRec = FooSetRec([$max=5], [$max=2]); +global mysetcomp: FooSetComp = FooSetComp(["test", 1], ["cool", 2]); + +print myset; +print mysetrec; +print mysetcomp; diff --git a/testing/btest/language/named-table-ctors.bro b/testing/btest/language/named-table-ctors.bro new file mode 100644 index 0000000000..83500488f1 --- /dev/null +++ b/testing/btest/language/named-table-ctors.bro @@ -0,0 +1,24 @@ +# @TEST-EXEC: bro -b %INPUT >out +# @TEST-EXEC: btest-diff out + +type MyRec: record { + min: count &optional; + max: count; +}; + +type FooTable: table[count] of string; +type FooTableRec: table[MyRec] of string; +type FooTableComp: table[string, count] of string; +type FooTableY: table[string] of double; + +global mytable: FooTable = FooTable([1] = "one", [5] = "five", [3] = "three"); +global mytablerec: FooTableRec = FooTableRec([[$max=5]] = "max5", [[$max=2]] = "max2"); +global mytablecomp: FooTableComp = FooTableComp(["test", 1] = "test1", ["cool", +2] = "cool2"); +global mytabley: FooTableY = FooTableY(["one"] = 1, ["two"] = 2, ["three"] = 3) &default=0; + +print mytable; +print mytablerec; +print mytablecomp; +print mytabley; +print mytabley["test"]; diff --git a/testing/btest/language/named-vector-ctors.bro b/testing/btest/language/named-vector-ctors.bro new file mode 100644 index 0000000000..1e0e1e9e55 --- /dev/null +++ b/testing/btest/language/named-vector-ctors.bro @@ -0,0 +1,19 @@ +# @TEST-EXEC: bro -b %INPUT >out +# @TEST-EXEC: btest-diff out + +type MyRec: record { + min: count &optional; + max: count; +}; + +type FooVector: vector of string; +type FooVectorD: vector of double; +type FooVectorRec: vector of MyRec; + +global myvec: FooVector = FooVector("one", "two", "three"); +global myvecd: FooVectorD = FooVectorD(1, 2, 3); +global myvecrec: FooVectorRec = FooVectorRec([$max=1], [$max=2], [$max=3]); + +print myvec; +print myvecd; +print myvecrec;