From ae5ae25000f1a97d2feda7396d8bf92b02920040 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 16 Feb 2025 13:59:50 +0100 Subject: [PATCH] Properties filters for EdgePairs and Texts too. --- src/db/db/gsiDeclDbEdgePairs.cc | 75 +++++++++++++++++++++++++++++--- src/db/db/gsiDeclDbTexts.cc | 75 +++++++++++++++++++++++++++++--- testdata/ruby/dbEdgePairsTest.rb | 54 ++++++++++++++++++++++- testdata/ruby/dbTextsTest.rb | 54 ++++++++++++++++++++++- 4 files changed, 244 insertions(+), 14 deletions(-) diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index aec39b3eb..64c01d5b1 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -30,6 +30,7 @@ #include "dbDeepEdgePairs.h" #include "dbEdgesUtils.h" #include "dbEdgePairFilters.h" +#include "dbPropertiesFilter.h" #include "gsiDeclDbContainerHelpers.h" @@ -39,8 +40,10 @@ namespace gsi // --------------------------------------------------------------------------------- // EdgePairFilter binding +typedef shape_filter_impl EdgePairFilterBase; + class EdgePairFilterImpl - : public shape_filter_impl + : public EdgePairFilterBase { public: EdgePairFilterImpl () { } @@ -67,8 +70,70 @@ private: EdgePairFilterImpl (const EdgePairFilterImpl &); }; -Class decl_EdgePairFilterImpl ("db", "EdgePairFilter", - EdgePairFilterImpl::method_decls (false) + +typedef db::generic_properties_filter EdgePairPropertiesFilter; + +static gsi::EdgePairFilterBase *make_ppf1 (const tl::Variant &name, const tl::Variant &value, bool inverse) +{ + return new EdgePairPropertiesFilter (name, value, inverse); +} + +static gsi::EdgePairFilterBase *make_ppf2 (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) +{ + return new EdgePairPropertiesFilter (name, from, to, inverse); +} + +static gsi::EdgePairFilterBase *make_pg (const tl::Variant &name, const std::string &glob, bool inverse, bool case_sensitive) +{ + tl::GlobPattern pattern (glob); + pattern.set_case_sensitive (case_sensitive); + return new EdgePairPropertiesFilter (name, pattern, inverse); +} + +Class decl_EdgePairFilterBase ("db", "EdgePairFilterBase", + gsi::EdgePairFilterBase::method_decls (true) + + gsi::constructor ("property_glob", &make_pg, gsi::arg ("name"), gsi::arg ("pattern"), gsi::arg ("inverse", false), gsi::arg ("case_sensitive", true), + "@brief Creates a single-valued property filter\n" + "@param name The name of the property to use.\n" + "@param value The glob pattern to match the property value against.\n" + "@param inverse If true, inverts the selection - i.e. all edge pairs without a matching property are selected.\n" + "@param case_sensitive If true, the match is case sensitive (the default), if false, the match is not case sensitive.\n" + "\n" + "Apply this filter with \\EdgePairs#filtered:\n" + "\n" + "@code\n" + "# edge_pairs is a EdgePairs object\n" + "# filtered_edge_pairs contains all edge pairs where the 'net' property starts with 'C':\n" + "filtered_edge_pairs = edge_pairs.filtered(RBA::EdgePairFilterBase::property_glob('net', 'C*'))\n" + "@/code\n" + "\n" + "This feature has been introduced in version 0.30." + ) + + gsi::constructor ("property_filter", &make_ppf1, gsi::arg ("name"), gsi::arg ("value"), gsi::arg ("inverse", false), + "@brief Creates a single-valued property filter\n" + "@param name The name of the property to use.\n" + "@param value The value against which the property is checked (exact match).\n" + "@param inverse If true, inverts the selection - i.e. all edge pairs without a property with the given name and value are selected.\n" + "\n" + "Apply this filter with \\EdgePairs#filtered. See \\property_glob for an example.\n" + "\n" + "This feature has been introduced in version 0.30." + ) + + gsi::constructor ("property_filter_bounded", &make_ppf2, gsi::arg ("name"), gsi::arg ("from"), gsi::arg ("to"), gsi::arg ("inverse", false), + "@brief Creates a single-valued property filter\n" + "@param name The name of the property to use.\n" + "@param from The lower value against which the property is checked or 'nil' if no lower bound shall be used.\n" + "@param to The upper value against which the property is checked or 'nil' if no upper bound shall be used.\n" + "@param inverse If true, inverts the selection - i.e. all edge pairs without a property with the given name and value range are selected.\n" + "\n" + "This version does a bounded match. The value of the propery needs to be larger or equal to 'from' and less than 'to'.\n" + "Apply this filter with \\EdgePairs#filtered. See \\property_glob for an example.\n" + "\n" + "This feature has been introduced in version 0.30." + ), + "@hide" +); + +Class decl_EdgePairFilterImpl (decl_EdgePairFilterBase, "db", "EdgePairFilter", callback ("selected", &EdgePairFilterImpl::issue_selected, &EdgePairFilterImpl::f_selected, gsi::arg ("text"), "@brief Selects an edge pair\n" "This method is the actual payload. It needs to be reimplemented in a derived class.\n" @@ -411,12 +476,12 @@ static size_t id (const db::EdgePairs *ep) return tl::id_of (ep->delegate ()); } -static db::EdgePairs filtered (const db::EdgePairs *r, const EdgePairFilterImpl *f) +static db::EdgePairs filtered (const db::EdgePairs *r, const gsi::EdgePairFilterBase *f) { return r->filtered (*f); } -static void filter (db::EdgePairs *r, const EdgePairFilterImpl *f) +static void filter (db::EdgePairs *r, const gsi::EdgePairFilterBase *f) { r->filter (*f); } diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index 46c516123..549b9d17b 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -27,6 +27,7 @@ #include "dbRegion.h" #include "dbDeepTexts.h" #include "dbTextsUtils.h" +#include "dbPropertiesFilter.h" #include "gsiDeclDbContainerHelpers.h" @@ -36,8 +37,10 @@ namespace gsi // --------------------------------------------------------------------------------- // TextFilter binding +typedef shape_filter_impl TextFilterBase; + class TextFilterImpl - : public shape_filter_impl + : public TextFilterBase { public: TextFilterImpl () { } @@ -64,8 +67,70 @@ private: TextFilterImpl (const TextFilterImpl &); }; -Class decl_TextFilterImpl ("db", "TextFilter", - TextFilterImpl::method_decls (false) + +typedef db::generic_properties_filter TextPropertiesFilter; + +static gsi::TextFilterBase *make_ppf1 (const tl::Variant &name, const tl::Variant &value, bool inverse) +{ + return new TextPropertiesFilter (name, value, inverse); +} + +static gsi::TextFilterBase *make_ppf2 (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) +{ + return new TextPropertiesFilter (name, from, to, inverse); +} + +static gsi::TextFilterBase *make_pg (const tl::Variant &name, const std::string &glob, bool inverse, bool case_sensitive) +{ + tl::GlobPattern pattern (glob); + pattern.set_case_sensitive (case_sensitive); + return new TextPropertiesFilter (name, pattern, inverse); +} + +Class decl_TextFilterBase ("db", "TextFilterBase", + gsi::TextFilterBase::method_decls (false) + + gsi::constructor ("property_glob", &make_pg, gsi::arg ("name"), gsi::arg ("pattern"), gsi::arg ("inverse", false), gsi::arg ("case_sensitive", true), + "@brief Creates a single-valued property filter\n" + "@param name The name of the property to use.\n" + "@param value The glob pattern to match the property value against.\n" + "@param inverse If true, inverts the selection - i.e. all texts without a matching property are selected.\n" + "@param case_sensitive If true, the match is case sensitive (the default), if false, the match is not case sensitive.\n" + "\n" + "Apply this filter with \\Texts#filtered:\n" + "\n" + "@code\n" + "# texts is a Texts object\n" + "# filtered_texts contains all texts where the 'net' property starts with 'C':\n" + "filtered_texts = texts.filtered(RBA::TextFilterBase::property_glob('net', 'C*'))\n" + "@/code\n" + "\n" + "This feature has been introduced in version 0.30." + ) + + gsi::constructor ("property_filter", &make_ppf1, gsi::arg ("name"), gsi::arg ("value"), gsi::arg ("inverse", false), + "@brief Creates a single-valued property filter\n" + "@param name The name of the property to use.\n" + "@param value The value against which the property is checked (exact match).\n" + "@param inverse If true, inverts the selection - i.e. all texts without a property with the given name and value are selected.\n" + "\n" + "Apply this filter with \\Texts#filtered. See \\property_glob for an example.\n" + "\n" + "This feature has been introduced in version 0.30." + ) + + gsi::constructor ("property_filter_bounded", &make_ppf2, gsi::arg ("name"), gsi::arg ("from"), gsi::arg ("to"), gsi::arg ("inverse", false), + "@brief Creates a single-valued property filter\n" + "@param name The name of the property to use.\n" + "@param from The lower value against which the property is checked or 'nil' if no lower bound shall be used.\n" + "@param to The upper value against which the property is checked or 'nil' if no upper bound shall be used.\n" + "@param inverse If true, inverts the selection - i.e. all texts without a property with the given name and value range are selected.\n" + "\n" + "This version does a bounded match. The value of the propery needs to be larger or equal to 'from' and less than 'to'.\n" + "Apply this filter with \\Texts#filtered. See \\property_glob for an example.\n" + "\n" + "This feature has been introduced in version 0.30." + ), + "@hide" +); + +Class decl_TextFilterImpl (decl_TextFilterBase, "db", "TextFilter", callback ("selected", &TextFilterImpl::issue_selected, &TextFilterImpl::f_selected, gsi::arg ("text"), "@brief Selects a text\n" "This method is the actual payload. It needs to be reimplemented in a derived class.\n" @@ -318,12 +383,12 @@ static size_t id (const db::Texts *t) return tl::id_of (t->delegate ()); } -static db::Texts filtered (const db::Texts *r, const TextFilterImpl *f) +static db::Texts filtered (const db::Texts *r, const gsi::TextFilterBase *f) { return r->filtered (*f); } -static void filter (db::Texts *r, const TextFilterImpl *f) +static void filter (db::Texts *r, const gsi::TextFilterBase *f) { r->filter (*f); } diff --git a/testdata/ruby/dbEdgePairsTest.rb b/testdata/ruby/dbEdgePairsTest.rb index 829b28d95..dfae93bf8 100644 --- a/testdata/ruby/dbEdgePairsTest.rb +++ b/testdata/ruby/dbEdgePairsTest.rb @@ -26,8 +26,8 @@ load("test_prologue.rb") # normalizes a specification string for region, edges etc. # such that the order of the objects becomes irrelevant def csort(s) - # splits at ");(" without consuming the brackets - s.split(/(?<=\));(?=\()/).sort.join(";") + # splits at ");(" or "};(" without consuming the brackets + s.split(/(?<=[\)\}]);(?=\()/).sort.join(";") end class PerpendicularEdgesFilter < RBA::EdgePairFilter @@ -598,6 +598,56 @@ class DBEdgePairs_TestClass < TestBase end + # properties + def test_prop_filters + + r = RBA::EdgePairs::new + r.insert(RBA::EdgePairWithProperties::new(RBA::EdgePair::new(RBA::Edge::new(0, 0, 100, 200), RBA::Edge::new(0, 10, 100, 210)), { "one" => -1 })) + r.insert(RBA::EdgePairWithProperties::new(RBA::EdgePair::new(RBA::Edge::new(1, 1, 101, 201), RBA::Edge::new(1, 11, 101, 211)), { "one" => 17 })) + r.insert(RBA::EdgePairWithProperties::new(RBA::EdgePair::new(RBA::Edge::new(2, 2, 102, 202), RBA::Edge::new(2, 12, 102, 212)), { "one" => 42 })) + + assert_equal(r.filtered(RBA::EdgePairFilter::property_filter("one", 11)).to_s, "") + assert_equal(r.filtered(RBA::EdgePairFilter::property_filter("two", 17)).to_s, "") + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter("one", 17)).to_s), csort("(1,1;101,201)/(1,11;101,211){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter("one", 17, true)).to_s), csort("(0,0;100,200)/(0,10;100,210){one=>-1};(2,2;102,202)/(2,12;102,212){one=>42}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter_bounded("one", 17, nil)).to_s), csort("(2,2;102,202)/(2,12;102,212){one=>42};(1,1;101,201)/(1,11;101,211){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter_bounded("one", 17, 18)).to_s), csort("(1,1;101,201)/(1,11;101,211){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter_bounded("one", 17, 18, true)).to_s), csort("(2,2;102,202)/(2,12;102,212){one=>42};(0,0;100,200)/(0,10;100,210){one=>-1}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter_bounded("one", nil, 18)).to_s), csort("(1,1;101,201)/(1,11;101,211){one=>17};(0,0;100,200)/(0,10;100,210){one=>-1}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_glob("one", "1*")).to_s), csort("(1,1;101,201)/(1,11;101,211){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_glob("one", "1*", true)).to_s), csort("(2,2;102,202)/(2,12;102,212){one=>42};(0,0;100,200)/(0,10;100,210){one=>-1}")) + + ly = RBA::Layout::new + top = ly.create_cell("TOP") + l1 = ly.layer(1, 0) + + s = top.shapes(l1) + s.insert(RBA::EdgePairWithProperties::new(RBA::EdgePair::new(RBA::Edge::new(0, 0, 100, 200), RBA::Edge::new(0, 10, 100, 210)), { "one" => -1 })) + s.insert(RBA::EdgePairWithProperties::new(RBA::EdgePair::new(RBA::Edge::new(1, 1, 101, 201), RBA::Edge::new(1, 11, 101, 211)), { "one" => 17 })) + s.insert(RBA::EdgePairWithProperties::new(RBA::EdgePair::new(RBA::Edge::new(2, 2, 102, 202), RBA::Edge::new(2, 12, 102, 212)), { "one" => 42 })) + + dss = RBA::DeepShapeStore::new + iter = top.begin_shapes_rec(l1) + iter.enable_properties() + r = RBA::EdgePairs::new(iter, dss) + + assert_equal(r.filtered(RBA::EdgePairFilter::property_filter("one", 11)).to_s, "") + assert_equal(r.filtered(RBA::EdgePairFilter::property_filter("two", 17)).to_s, "") + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter("one", 17)).to_s), csort("(1,1;101,201)/(1,11;101,211){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter("one", 17, true)).to_s), csort("(0,0;100,200)/(0,10;100,210){one=>-1};(2,2;102,202)/(2,12;102,212){one=>42}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter_bounded("one", 17, nil)).to_s), csort("(2,2;102,202)/(2,12;102,212){one=>42};(1,1;101,201)/(1,11;101,211){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter_bounded("one", 17, 18)).to_s), csort("(1,1;101,201)/(1,11;101,211){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter_bounded("one", 17, 18, true)).to_s), csort("(2,2;102,202)/(2,12;102,212){one=>42};(0,0;100,200)/(0,10;100,210){one=>-1}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_filter_bounded("one", nil, 18)).to_s), csort("(1,1;101,201)/(1,11;101,211){one=>17};(0,0;100,200)/(0,10;100,210){one=>-1}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_glob("one", "1*")).to_s), csort("(1,1;101,201)/(1,11;101,211){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgePairFilter::property_glob("one", "1*", true)).to_s), csort("(2,2;102,202)/(2,12;102,212){one=>42};(0,0;100,200)/(0,10;100,210){one=>-1}")) + + rr = r.dup + rr.filter(RBA::EdgePairFilter::property_filter("one", 17)) + assert_equal(csort(rr.to_s), csort("(1,1;101,201)/(1,11;101,211){one=>17}")) + + end + end diff --git a/testdata/ruby/dbTextsTest.rb b/testdata/ruby/dbTextsTest.rb index 0ddfabc02..bc7b70875 100644 --- a/testdata/ruby/dbTextsTest.rb +++ b/testdata/ruby/dbTextsTest.rb @@ -26,8 +26,8 @@ load("test_prologue.rb") # normalizes a specification string for region, edges etc. # such that the order of the objects becomes irrelevant def csort(s) - # splits at ");(" without consuming the brackets - s.split(/(?<=\));(?=\()/).sort.join(";") + # splits at ");(" or "};(" without consuming the brackets + s.split(/(?<=[\)\}]);(?=\()/).sort.join(";") end class TextStringLengthFilter < RBA::TextFilter @@ -467,6 +467,56 @@ class DBTexts_TestClass < TestBase end + # properties + def test_prop_filters + + r = RBA::Texts::new + r.insert(RBA::TextWithProperties::new(RBA::Text::new("abc", RBA::Trans::R0), { "one" => -1 })) + r.insert(RBA::TextWithProperties::new(RBA::Text::new("uvw", RBA::Trans::R0), { "one" => 17 })) + r.insert(RBA::TextWithProperties::new(RBA::Text::new("xyz", RBA::Trans::R0), { "one" => 42 })) + + assert_equal(r.filtered(RBA::TextFilter::property_filter("one", 11)).to_s, "") + assert_equal(r.filtered(RBA::TextFilter::property_filter("two", 17)).to_s, "") + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter("one", 17)).to_s), csort("('uvw',r0 0,0){one=>17}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter("one", 17, true)).to_s), csort("('abc',r0 0,0){one=>-1};('xyz',r0 0,0){one=>42}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter_bounded("one", 17, nil)).to_s), csort("('xyz',r0 0,0){one=>42};('uvw',r0 0,0){one=>17}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter_bounded("one", 17, 18)).to_s), csort("('uvw',r0 0,0){one=>17}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter_bounded("one", 17, 18, true)).to_s), csort("('xyz',r0 0,0){one=>42};('abc',r0 0,0){one=>-1}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter_bounded("one", nil, 18)).to_s), csort("('uvw',r0 0,0){one=>17};('abc',r0 0,0){one=>-1}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_glob("one", "1*")).to_s), csort("('uvw',r0 0,0){one=>17}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_glob("one", "1*", true)).to_s), csort("('xyz',r0 0,0){one=>42};('abc',r0 0,0){one=>-1}")) + + ly = RBA::Layout::new + top = ly.create_cell("TOP") + l1 = ly.layer(1, 0) + + s = top.shapes(l1) + s.insert(RBA::TextWithProperties::new(RBA::Text::new("abc", RBA::Trans::R0), { "one" => -1 })) + s.insert(RBA::TextWithProperties::new(RBA::Text::new("uvw", RBA::Trans::R0), { "one" => 17 })) + s.insert(RBA::TextWithProperties::new(RBA::Text::new("xyz", RBA::Trans::R0), { "one" => 42 })) + + dss = RBA::DeepShapeStore::new + iter = top.begin_shapes_rec(l1) + iter.enable_properties() + r = RBA::Texts::new(iter, dss) + + assert_equal(r.filtered(RBA::TextFilter::property_filter("one", 11)).to_s, "") + assert_equal(r.filtered(RBA::TextFilter::property_filter("two", 17)).to_s, "") + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter("one", 17)).to_s), csort("('uvw',r0 0,0){one=>17}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter("one", 17, true)).to_s), csort("('abc',r0 0,0){one=>-1};('xyz',r0 0,0){one=>42}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter_bounded("one", 17, nil)).to_s), csort("('xyz',r0 0,0){one=>42};('uvw',r0 0,0){one=>17}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter_bounded("one", 17, 18)).to_s), csort("('uvw',r0 0,0){one=>17}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter_bounded("one", 17, 18, true)).to_s), csort("('xyz',r0 0,0){one=>42};('abc',r0 0,0){one=>-1}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_filter_bounded("one", nil, 18)).to_s), csort("('uvw',r0 0,0){one=>17};('abc',r0 0,0){one=>-1}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_glob("one", "1*")).to_s), csort("('uvw',r0 0,0){one=>17}")) + assert_equal(csort(r.filtered(RBA::TextFilter::property_glob("one", "1*", true)).to_s), csort("('xyz',r0 0,0){one=>42};('abc',r0 0,0){one=>-1}")) + + rr = r.dup + rr.filter(RBA::TextFilter::property_filter("one", 17)) + assert_equal(csort(rr.to_s), csort("('uvw',r0 0,0){one=>17}")) + + end + end