From 6449b0ab9e87cd61bc180fc6aafc9a5fc5ddfe4e Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Sat, 23 Jun 2018 14:46:47 -0700 Subject: [PATCH 1/5] mirroring previous topic/vern/set-ops to get branch up to date, since I'm a n00b --- src/Expr.cc | 77 +++++++++++++++++++++++++++++++++++++++++++++-------- src/Expr.h | 3 +++ src/Val.cc | 48 +++++++++++++++++++++++++++++++-- src/Val.h | 6 +++++ 4 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/Expr.cc b/src/Expr.cc index 1ab82853c3..617a7637df 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -663,6 +663,9 @@ Val* BinaryExpr::Fold(Val* v1, Val* v2) const if ( it == TYPE_INTERNAL_STRING ) return StringFold(v1, v2); + if ( v1->Type()->IsSet() ) + return SetFold(v1, v2); + if ( it == TYPE_INTERNAL_ADDR ) return AddrFold(v1, v2); @@ -849,6 +852,32 @@ Val* BinaryExpr::StringFold(Val* v1, Val* v2) const return new Val(result, TYPE_BOOL); } +Val* BinaryExpr::SetFold(Val* v1, Val* v2) const + { + TableVal* tv1 = v1->AsTableVal(); + TableVal* tv2 = v2->AsTableVal(); + + if ( tag != EXPR_AND && tag != EXPR_OR && tag != EXPR_SUB ) + BadTag("BinaryExpr::SetFold"); + + // TableVal* result = new TableVal(v1->Type()->AsTableType()); + TableVal* result = v1->Clone()->AsTableVal(); + + if ( tag == EXPR_OR ) + { + if ( ! tv2->AddTo(result, false, false) ) + reporter->InternalError("set union failed to type check"); + } + + else if ( tag == EXPR_SUB ) + { + if ( ! tv2->RemoveFrom(result) ) + reporter->InternalError("set difference failed to type check"); + } + + return result; + } + Val* BinaryExpr::AddrFold(Val* v1, Val* v2) const { IPAddr a1 = v1->AsAddr(); @@ -1421,24 +1450,39 @@ SubExpr::SubExpr(Expr* arg_op1, Expr* arg_op2) if ( IsError() ) return; - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); BroType* base_result_type = 0; if ( bt1 == TYPE_TIME && bt2 == TYPE_INTERVAL ) base_result_type = base_type(bt1); + else if ( bt1 == TYPE_TIME && bt2 == TYPE_TIME ) SetType(base_type(TYPE_INTERVAL)); + else if ( bt1 == TYPE_INTERVAL && bt2 == TYPE_INTERVAL ) base_result_type = base_type(bt1); + + else if ( t1->IsSet() && t2->IsSet() ) + { + if ( same_type(t1, t2) ) + SetType(op1->Type()->Ref()); + else + ExprError("incompatible \"set\" operands"); + } + else if ( BothArithmetic(bt1, bt2) ) PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2)); + else ExprError("requires arithmetic operands"); @@ -1864,13 +1908,16 @@ BitExpr::BitExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) if ( IsError() ) return; - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); if ( (bt1 == TYPE_COUNT || bt1 == TYPE_COUNTER) && (bt2 == TYPE_COUNT || bt2 == TYPE_COUNTER) ) @@ -1883,8 +1930,16 @@ BitExpr::BitExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) SetType(base_type(TYPE_COUNT)); } + else if ( t1->IsSet() && t2->IsSet() ) + { + if ( same_type(t1, t2) ) + SetType(op1->Type()->Ref()); + else + ExprError("incompatible \"set\" operands"); + } + else - ExprError("requires \"count\" operands"); + ExprError("requires \"count\" or compatible \"set\" operands"); } IMPLEMENT_SERIAL(BitExpr, SER_BIT_EXPR); diff --git a/src/Expr.h b/src/Expr.h index 9fc9aa15ed..a8c890b675 100644 --- a/src/Expr.h +++ b/src/Expr.h @@ -329,6 +329,9 @@ protected: // Same for when the constants are strings. virtual Val* StringFold(Val* v1, Val* v2) const; + // Same for when the constants are sets. + virtual Val* SetFold(Val* v1, Val* v2) const; + // Same for when the constants are addresses or subnets. virtual Val* AddrFold(Val* v1, Val* v2) const; virtual Val* SubNetFold(Val* v1, Val* v2) const; diff --git a/src/Val.cc b/src/Val.cc index 4da4a35d48..540719ef14 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1704,9 +1704,18 @@ int TableVal::RemoveFrom(Val* val) const HashKey* k; while ( tbl->NextEntry(k, c) ) { - Val* index = RecoverIndex(k); + // ### The following code appears to be a complete + // no-op. Commented out 8+ years after it was + // introduced. -VP 22Jun18 + // Val* index = RecoverIndex(k); + // + // Unref(index); - Unref(index); + // Not sure that this is 100% sound, since the HashKey + // comes from one table but is being used in another. + // OTOH, they are both the same type, so as long as + // we don't have hash keys that are keyed per dictionary, + // it should work ... Unref(t->Delete(k)); delete k; } @@ -1714,6 +1723,41 @@ int TableVal::RemoveFrom(Val* val) const return 1; } +TableVal* TableVal::Intersect(const TableVal* tv) const + { + TableVal* result = new TableVal(table_type); + + const PDict(TableEntryVal)* t1 = tv->AsTable(); + const PDict(TableEntryVal)* t2 = AsTable(); + const PDict(TableEntryVal)* t3 = result->AsTable(); + + // Figure out which is smaller. + if ( t1->Length() > t2->Length() ) + { // Swap. + const PDict(TableEntryVal)* t3 = t1; + t1 = t2; + t2 = t3; + } + + IterCookie* c = t1->InitForIteration(); + HashKey* k; + while ( t1->NextEntry(k, c) ) + { +//### // Here we leverage the same assumption about consistent +//### // hashes as in TableVal::RemoveFrom above. +//### if ( t2->Lookup(k) ) +//### { +//### Val* index = RecoverIndex(); +//### result-> +//### +//### Unref(index); +//### Unref(t->Delete(k)); +//### delete k; + } + + return result; + } + int TableVal::ExpandAndInit(Val* index, Val* new_val) { BroType* index_type = index->Type(); diff --git a/src/Val.h b/src/Val.h index 771ed40dd1..ef2a8eefd6 100644 --- a/src/Val.h +++ b/src/Val.h @@ -809,6 +809,12 @@ public: // Returns true if the addition typechecked, false if not. int RemoveFrom(Val* v) const override; + // Returns a new table that is the intersection of this + // table and the given table. Intersection is just done + // on index, not on yield value, so this really only makes + // sense for sets. + TableVal* Intersect(const TableVal* v) const; + // Expands any lists in the index into multiple initializations. // Returns true if the initializations typecheck, false if not. int ExpandAndInit(Val* index, Val* new_val); From 072a25df0f353009fb4655c39eb2efca7a000ab4 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Sun, 24 Jun 2018 10:43:58 -0700 Subject: [PATCH 2/5] set intersection implemented --- src/Expr.cc | 6 ++++++ src/Val.cc | 18 +++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Expr.cc b/src/Expr.cc index 617a7637df..ed14eb6045 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -860,6 +860,9 @@ Val* BinaryExpr::SetFold(Val* v1, Val* v2) const if ( tag != EXPR_AND && tag != EXPR_OR && tag != EXPR_SUB ) BadTag("BinaryExpr::SetFold"); + if ( tag == EXPR_AND ) + return tv1->Intersect(tv2); + // TableVal* result = new TableVal(v1->Type()->AsTableType()); TableVal* result = v1->Clone()->AsTableVal(); @@ -875,6 +878,9 @@ Val* BinaryExpr::SetFold(Val* v1, Val* v2) const reporter->InternalError("set difference failed to type check"); } + else + BadTag("BinaryExpr::SetFold", expr_name(tag)); + return result; } diff --git a/src/Val.cc b/src/Val.cc index 540719ef14..d6bcdae4a6 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1729,7 +1729,7 @@ TableVal* TableVal::Intersect(const TableVal* tv) const const PDict(TableEntryVal)* t1 = tv->AsTable(); const PDict(TableEntryVal)* t2 = AsTable(); - const PDict(TableEntryVal)* t3 = result->AsTable(); + PDict(TableEntryVal)* t3 = result->AsNonConstTable(); // Figure out which is smaller. if ( t1->Length() > t2->Length() ) @@ -1743,16 +1743,12 @@ TableVal* TableVal::Intersect(const TableVal* tv) const HashKey* k; while ( t1->NextEntry(k, c) ) { -//### // Here we leverage the same assumption about consistent -//### // hashes as in TableVal::RemoveFrom above. -//### if ( t2->Lookup(k) ) -//### { -//### Val* index = RecoverIndex(); -//### result-> -//### -//### Unref(index); -//### Unref(t->Delete(k)); -//### delete k; + // Here we leverage the same assumption about consistent + // hashes as in TableVal::RemoveFrom above. + if ( t2->Lookup(k) ) + t3->Insert(k, new TableEntryVal(0)); + + delete k; } return result; From e416d34f1f3cb291fa164e00f531ba52dde47678 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Fri, 6 Jul 2018 13:46:06 -0700 Subject: [PATCH 3/5] bug fix for set intersection --- src/Val.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Val.cc b/src/Val.cc index d6bcdae4a6..48458254f1 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1734,9 +1734,9 @@ TableVal* TableVal::Intersect(const TableVal* tv) const // Figure out which is smaller. if ( t1->Length() > t2->Length() ) { // Swap. - const PDict(TableEntryVal)* t3 = t1; + const PDict(TableEntryVal)* tmp = t1; t1 = t2; - t2 = t3; + t2 = tmp; } IterCookie* c = t1->InitForIteration(); From 2a8ea87c9fe048657edcf257ced11d89a8067afb Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Fri, 6 Jul 2018 16:22:06 -0700 Subject: [PATCH 4/5] implemented set relationals --- src/Expr.cc | 95 +++++++++++++++++++++++++++++++++++++++-------------- src/Val.cc | 68 +++++++++++++++++++++++++++++++++----- src/Val.h | 10 ++++++ 3 files changed, 140 insertions(+), 33 deletions(-) diff --git a/src/Expr.cc b/src/Expr.cc index ed14eb6045..d3b22503cb 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -856,32 +856,55 @@ Val* BinaryExpr::SetFold(Val* v1, Val* v2) const { TableVal* tv1 = v1->AsTableVal(); TableVal* tv2 = v2->AsTableVal(); + TableVal* result; + bool res; - if ( tag != EXPR_AND && tag != EXPR_OR && tag != EXPR_SUB ) - BadTag("BinaryExpr::SetFold"); - - if ( tag == EXPR_AND ) + switch ( tag ) { + case EXPR_AND: return tv1->Intersect(tv2); - // TableVal* result = new TableVal(v1->Type()->AsTableType()); - TableVal* result = v1->Clone()->AsTableVal(); + case EXPR_OR: + // TableVal* result = new TableVal(v1->Type()->AsTableType()); + result = v1->Clone()->AsTableVal(); - if ( tag == EXPR_OR ) - { if ( ! tv2->AddTo(result, false, false) ) reporter->InternalError("set union failed to type check"); - } + return result; + + case EXPR_SUB: + result = v1->Clone()->AsTableVal(); - else if ( tag == EXPR_SUB ) - { if ( ! tv2->RemoveFrom(result) ) reporter->InternalError("set difference failed to type check"); - } + return result; - else + case EXPR_EQ: + res = tv1->EqualTo(tv2); + break; + + case EXPR_NE: + res = ! tv1->EqualTo(tv2); + break; + + case EXPR_LT: + res = tv1->IsSubsetOf(tv2) && tv1->Size() < tv2->Size(); + break; + + case EXPR_LE: + res = tv1->IsSubsetOf(tv2); + break; + + case EXPR_GE: + case EXPR_GT: + // These should't happen due to canonicalization. + reporter->InternalError("confusion over canonicalization in set comparison"); + + default: BadTag("BinaryExpr::SetFold", expr_name(tag)); + return 0; + } - return result; + return new Val(res, TYPE_BOOL); } Val* BinaryExpr::AddrFold(Val* v1, Val* v2) const @@ -1970,13 +1993,16 @@ EqExpr::EqExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) Canonicize(); - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); if ( is_vector(op1) || is_vector(op2) ) SetType(new VectorType(base_type(TYPE_BOOL))); @@ -2006,10 +2032,20 @@ EqExpr::EqExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) break; case TYPE_ENUM: - if ( ! same_type(op1->Type(), op2->Type()) ) + if ( ! same_type(t1, t2) ) ExprError("illegal enum comparison"); break; + case TYPE_TABLE: + if ( t1->IsSet() && t2->IsSet() ) + { + if ( ! same_type(t1, t2) ) + ExprError("incompatible sets in comparison"); + break; + } + + // FALL THROUGH + default: ExprError("illegal comparison"); } @@ -2072,13 +2108,16 @@ RelExpr::RelExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) Canonicize(); - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); if ( is_vector(op1) || is_vector(op2) ) SetType(new VectorType(base_type(TYPE_BOOL))); @@ -2088,6 +2127,12 @@ RelExpr::RelExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) if ( BothArithmetic(bt1, bt2) ) PromoteOps(max_type(bt1, bt2)); + else if ( t1->IsSet() && t2->IsSet() ) + { + if ( ! same_type(t1, t2) ) + ExprError("incompatible sets in comparison"); + } + else if ( bt1 != bt2 ) ExprError("operands must be of the same type"); diff --git a/src/Val.cc b/src/Val.cc index 48458254f1..02e88cd3b4 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1727,16 +1727,16 @@ TableVal* TableVal::Intersect(const TableVal* tv) const { TableVal* result = new TableVal(table_type); + const PDict(TableEntryVal)* t0 = AsTable(); const PDict(TableEntryVal)* t1 = tv->AsTable(); - const PDict(TableEntryVal)* t2 = AsTable(); - PDict(TableEntryVal)* t3 = result->AsNonConstTable(); + PDict(TableEntryVal)* t2 = result->AsNonConstTable(); - // Figure out which is smaller. - if ( t1->Length() > t2->Length() ) + // Figure out which is smaller; assign it to t1. + if ( t1->Length() > t0->Length() ) { // Swap. const PDict(TableEntryVal)* tmp = t1; - t1 = t2; - t2 = tmp; + t1 = t0; + t0 = tmp; } IterCookie* c = t1->InitForIteration(); @@ -1745,8 +1745,8 @@ TableVal* TableVal::Intersect(const TableVal* tv) const { // Here we leverage the same assumption about consistent // hashes as in TableVal::RemoveFrom above. - if ( t2->Lookup(k) ) - t3->Insert(k, new TableEntryVal(0)); + if ( t0->Lookup(k) ) + t2->Insert(k, new TableEntryVal(0)); delete k; } @@ -1754,6 +1754,58 @@ TableVal* TableVal::Intersect(const TableVal* tv) const return result; } +bool TableVal::EqualTo(const TableVal* tv) const + { + const PDict(TableEntryVal)* t0 = AsTable(); + const PDict(TableEntryVal)* t1 = tv->AsTable(); + + if ( t0->Length() != t1->Length() ) + return false; + + IterCookie* c = t0->InitForIteration(); + HashKey* k; + while ( t0->NextEntry(k, c) ) + { + // Here we leverage the same assumption about consistent + // hashes as in TableVal::RemoveFrom above. + if ( ! t1->Lookup(k) ) + { + delete k; + return false; + } + + delete k; + } + + return true; + } + +bool TableVal::IsSubsetOf(const TableVal* tv) const + { + const PDict(TableEntryVal)* t0 = AsTable(); + const PDict(TableEntryVal)* t1 = tv->AsTable(); + + if ( t0->Length() > t1->Length() ) + return false; + + IterCookie* c = t0->InitForIteration(); + HashKey* k; + while ( t0->NextEntry(k, c) ) + { + // Here we leverage the same assumption about consistent + // hashes as in TableVal::RemoveFrom above. + if ( ! t1->Lookup(k) ) + { + delete k; + return false; + } + + delete k; + } + + return true; + } + int TableVal::ExpandAndInit(Val* index, Val* new_val) { BroType* index_type = index->Type(); diff --git a/src/Val.h b/src/Val.h index ef2a8eefd6..32ce6a0187 100644 --- a/src/Val.h +++ b/src/Val.h @@ -815,6 +815,16 @@ public: // sense for sets. TableVal* Intersect(const TableVal* v) const; + // Returns true if this set contains the same members as the + // given set. Note that comparisons are done using hash keys, + // so errors can arise for compound sets such as sets-of-sets. + // See https://bro-tracker.atlassian.net/browse/BIT-1949. + bool EqualTo(const TableVal* v) const; + + // Returns true if this set is a subset (not necessarily proper) + // of the given set. + bool IsSubsetOf(const TableVal* v) const; + // Expands any lists in the index into multiple initializations. // Returns true if the initializations typecheck, false if not. int ExpandAndInit(Val* index, Val* new_val); From 86cd484759d09f7fea89e8f0281e90bcbe24f8df Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Fri, 20 Jul 2018 08:57:37 -0700 Subject: [PATCH 5/5] documentation, test suite update --- NEWS | 9 +++++ doc/script-reference/types.rst | 9 +++++ testing/btest/Baseline/language.set/out | 27 +++++++++++++++ testing/btest/language/set.bro | 45 +++++++++++++++++++++++++ 4 files changed, 90 insertions(+) diff --git a/NEWS b/NEWS index 93a28cb200..f66cdd617e 100644 --- a/NEWS +++ b/NEWS @@ -249,6 +249,15 @@ New Functionality '^' are binary "and", "or" and "xor" operators, and '~' is a unary ones-complement operator. +- Added support for set union, intersection, difference, and comparison + operations. The corresponding operators for the first three are + "s1 | s2", "s1 & s2", and "s1 - s2". Relationals are in terms + of subsets, so "s1 < s2" yields true if s1 is a proper subset of s2 + and "s1 == s2" if the two sets have exactly the same elements. + "s1 <= s2" holds for subsets or equality, and similarly "s1 != s2", + "s1 > s2", and "s1 >= s2" have the expected meanings in terms + of non-equality, proper superset, and superset-or-equal. + Changed Functionality --------------------- diff --git a/doc/script-reference/types.rst b/doc/script-reference/types.rst index 44dcbbdfb8..5459ae8514 100644 --- a/doc/script-reference/types.rst +++ b/doc/script-reference/types.rst @@ -526,6 +526,15 @@ Here is a more detailed description of each type: |s| + You can compute the union, intersection, or difference of two sets + using the ``|``, ``&``, and ``-`` operators. You can compare + sets for equality (they have exactly the same elements) using ``==``. + The ``<`` operator returns ``T`` if the lefthand operand is a proper + subset of the righthand operand. Similarly, ``<=`` returns ``T`` + if the lefthand operator is a subset (not necessarily proper, i.e., + it may be equal to the righthand operand). The operators ``!=``, ``>`` + and ``>=`` provide the expected complementary operations. + See the :bro:keyword:`for` statement for info on how to iterate over the elements in a set. diff --git a/testing/btest/Baseline/language.set/out b/testing/btest/Baseline/language.set/out index fc157cf7d9..0128420cbf 100644 --- a/testing/btest/Baseline/language.set/out +++ b/testing/btest/Baseline/language.set/out @@ -42,3 +42,30 @@ remove element (PASS) !in operator (PASS) remove element (PASS) !in operator (PASS) +union (PASS) +intersection (FAIL) +difference (PASS) +difference (PASS) +union/inter. (PASS) +relational (PASS) +relational (PASS) +subset (FAIL) +subset (FAIL) +subset (PASS) +superset (FAIL) +superset (FAIL) +superset (FAIL) +superset (PASS) +non-ordering (FAIL) +non-ordering (PASS) +superset (PASS) +superset (FAIL) +superset (PASS) +superset (PASS) +superset (PASS) +superset (FAIL) +equality (PASS) +equality (FAIL) +non-equality (PASS) +equality (FAIL) +magnitude (FAIL) diff --git a/testing/btest/language/set.bro b/testing/btest/language/set.bro index d1eef7e6f0..56cd649b49 100644 --- a/testing/btest/language/set.bro +++ b/testing/btest/language/set.bro @@ -136,5 +136,50 @@ event bro_init() delete sg3["curly"]; test_case( "remove element", |sg3| == 3 ); test_case( "!in operator", "curly" !in sg3 ); + + + local a = set(1,5,7,9,8,14); + local b = set(1,7,9,2); + + local a_plus_b = set(1,2,5,7,9,8,14); + local a_also_b = set(1,7,9); + local a_sans_b = set(5,8,14); + local b_sans_a = set(2); + + local a_or_b = a | b; + local a_and_b = a & b; + + test_case( "union", a_or_b == a_plus_b ); + test_case( "intersection", a_and_b == a_plus_b ); + test_case( "difference", a - b == a_sans_b ); + test_case( "difference", b - a == b_sans_a ); + + test_case( "union/inter.", |b & set(1,7,9,2)| == |b | set(1,7,2,9)| ); + test_case( "relational", |b & a_or_b| == |b| && |b| < |a_or_b| ); + test_case( "relational", b < a_or_b && a < a_or_b && a_or_b > a_and_b ); + + test_case( "subset", b < a ); + test_case( "subset", a < b ); + test_case( "subset", b < (a | set(2)) ); + test_case( "superset", b > a ); + test_case( "superset", b > (a | set(2)) ); + test_case( "superset", b | set(8, 14, 5) > (a | set(2)) ); + test_case( "superset", b | set(8, 14, 99, 5) > (a | set(2)) ); + + test_case( "non-ordering", (a <= b) || (a >= b) ); + test_case( "non-ordering", (a <= a_or_b) && (a_or_b >= b) ); + + test_case( "superset", (b | set(14, 5)) > a - set(8) ); + test_case( "superset", (b | set(14)) > a - set(8) ); + test_case( "superset", (b | set(14)) > a - set(8,5) ); + test_case( "superset", b >= a - set(5,8,14) ); + test_case( "superset", b > a - set(5,8,14) ); + test_case( "superset", (b - set(2)) > a - set(5,8,14) ); + test_case( "equality", a == a | set(5) ); + test_case( "equality", a == a | set(5,11) ); + test_case( "non-equality", a != a | set(5,11) ); + test_case( "equality", a == a | set(5,11) ); + + test_case( "magnitude", |a_and_b| == |a_or_b|); }