diff --git a/src/db/db/dbLayoutToNetlist.cc b/src/db/db/dbLayoutToNetlist.cc index 9232b78ea..15d0d5c12 100644 --- a/src/db/db/dbLayoutToNetlist.cc +++ b/src/db/db/dbLayoutToNetlist.cc @@ -284,7 +284,7 @@ void LayoutToNetlist::connect_impl (const db::ShapeCollection &a, const db::Shap m_conn.connect (dla.layer (), dlb.layer ()); } -void LayoutToNetlist::connect_global_impl (const db::ShapeCollection &l, const std::string &gn) +size_t LayoutToNetlist::connect_global_impl (const db::ShapeCollection &l, const std::string &gn) { if (m_netlist_extracted) { throw tl::Exception (tl::to_string (tr ("The netlist has already been extracted"))); @@ -297,7 +297,7 @@ void LayoutToNetlist::connect_global_impl (const db::ShapeCollection &l, const s db::DeepLayer dl = deep_layer_of (l); m_dlrefs.insert (dl); - m_conn.connect_global (dl.layer (), gn); + return m_conn.connect_global (dl.layer (), gn); } const std::string &LayoutToNetlist::global_net_name (size_t id) const diff --git a/src/db/db/dbLayoutToNetlist.h b/src/db/db/dbLayoutToNetlist.h index 4d103e66e..a7875863c 100644 --- a/src/db/db/dbLayoutToNetlist.h +++ b/src/db/db/dbLayoutToNetlist.h @@ -398,18 +398,18 @@ public: * @brief Connects the given layer with a global net with the given name * Returns the global net ID */ - void connect_global (const db::Region &l, const std::string &gn) + size_t connect_global (const db::Region &l, const std::string &gn) { - connect_global_impl (l, gn); + return connect_global_impl (l, gn); } /** * @brief Connects the given text layer with a global net with the given name * Returns the global net ID */ - void connect_global (const db::Texts &l, const std::string &gn) + size_t connect_global (const db::Texts &l, const std::string &gn) { - connect_global_impl (l, gn); + return connect_global_impl (l, gn); } /** @@ -872,7 +872,7 @@ private: db::properties_id_type make_netname_propid (db::Layout &ly, const tl::Variant &netname_prop, const db::Net &net) const; db::CellMapping make_cell_mapping_into (db::Layout &layout, db::Cell &cell, const std::vector *nets, bool with_device_cells); void connect_impl (const db::ShapeCollection &a, const db::ShapeCollection &b); - void connect_global_impl (const db::ShapeCollection &l, const std::string &gn); + size_t connect_global_impl (const db::ShapeCollection &l, const std::string &gn); // implementation of NetlistManipulationCallbacks virtual size_t link_net_to_parent_circuit (const Net *subcircuit_net, Circuit *parent_circuit, const DCplxTrans &trans); diff --git a/src/db/db/gsiDeclDbLayoutToNetlist.cc b/src/db/db/gsiDeclDbLayoutToNetlist.cc index cedc464ab..1f46e9139 100644 --- a/src/db/db/gsiDeclDbLayoutToNetlist.cc +++ b/src/db/db/gsiDeclDbLayoutToNetlist.cc @@ -357,12 +357,12 @@ Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", "\n" "This variant has been introduced in version 0.27.\n" ) + - gsi::method ("connect_global", (void (db::LayoutToNetlist::*) (const db::Region &, const std::string &)) &db::LayoutToNetlist::connect_global, gsi::arg ("l"), gsi::arg ("global_net_name"), + gsi::method ("connect_global", (size_t (db::LayoutToNetlist::*) (const db::Region &, const std::string &)) &db::LayoutToNetlist::connect_global, gsi::arg ("l"), gsi::arg ("global_net_name"), "@brief Defines a connection of the given layer with a global net.\n" "This method returns the ID of the global net. Use \\global_net_name to get " "the name back from the ID." ) + - gsi::method ("connect_global", (void (db::LayoutToNetlist::*) (const db::Texts &, const std::string &)) &db::LayoutToNetlist::connect_global, gsi::arg ("l"), gsi::arg ("global_net_name"), + gsi::method ("connect_global", (size_t (db::LayoutToNetlist::*) (const db::Texts &, const std::string &)) &db::LayoutToNetlist::connect_global, gsi::arg ("l"), gsi::arg ("global_net_name"), "@brief Defines a connection of the given text layer with a global net.\n" "This method returns the ID of the global net. Use \\global_net_name to get " "the name back from the ID." diff --git a/src/lay/lay/doc/programming/ruby_binding.xml b/src/lay/lay/doc/programming/ruby_binding.xml index 07cc293e2..7449de54a 100644 --- a/src/lay/lay/doc/programming/ruby_binding.xml +++ b/src/lay/lay/doc/programming/ruby_binding.xml @@ -278,6 +278,29 @@ event._manage and pointers/references and also supports "out" parameters.

+

"void" as return value

+ +

+ While a "void" return value indicates "no return value" in C++, this concept is not + common in Ruby. Ruby methods will always return the last value generated in + a method. +

+ +

+ For consistency, KLayout's Ruby binding returns "self" from methods in this + case. This allows chaining of methods in many cases and fosters compact code: +

+ +
// C++
+class A {
+public:
+  virtual void f() { ... }
+  virtual void g() { ... }
+};
+
+# Ruby
+a = A::new.f.g
+

References and pointers to simple types (FixNum, Float, String)

@@ -499,6 +522,16 @@ public: a = A::new a.each { |i| ... } +

+ If no block is given, an Enumerator object is created. Enumerators + are a Ruby feature. Enumerators support many convenient methods + like sort, inject, collect, select etc. Here is an example: +

+ +
+# turns all elements returned by the iterator into strings and sorts them
+sorted = a.each.collect(&:to_s).sort
+

Iterators match very well between C++ and Ruby so there are no real issues here. The return type of the iterators is mapped to Ruby's block arguments using the diff --git a/src/pya/pya/pyaModule.cc b/src/pya/pya/pyaModule.cc index 31d3961d3..5c16ce6e3 100644 --- a/src/pya/pya/pyaModule.cc +++ b/src/pya/pya/pyaModule.cc @@ -686,6 +686,15 @@ property_name_from_id (int mid, PyObject *self) return cls_decl->name () + "." + mt->property_name (mid); } +static gsi::ArgType create_void_type () +{ + gsi::ArgType at; + at.init (); + return at; +} + +static gsi::ArgType s_void_type = create_void_type (); + static PyObject * get_return_value (PYAObjectBase *self, gsi::SerialArgs &retlist, const gsi::MethodBase *meth, tl::Heap &heap) { @@ -696,6 +705,12 @@ get_return_value (PYAObjectBase *self, gsi::SerialArgs &retlist, const gsi::Meth gsi::IterAdaptorAbstractBase *iter = (gsi::IterAdaptorAbstractBase *) retlist.read (heap); ret = (PyObject *) PYAIteratorObject::create (self ? self->py_object () : 0, iter, &meth->ret_type ()); + } else if (meth->ret_type () == s_void_type && self != 0) { + + // simple, yet magical :) + ret = self->py_object (); + Py_INCREF (ret); + } else { ret = pop_arg (meth->ret_type (), retlist, self, heap).release (); diff --git a/src/rba/rba/rba.cc b/src/rba/rba/rba.cc index acbb5e353..3ee9dd964 100644 --- a/src/rba/rba/rba.cc +++ b/src/rba/rba/rba.cc @@ -89,7 +89,8 @@ public: { std::vector bt; bt.push_back (tl::BacktraceElement (rb_sourcefile (), rb_sourceline ())); - rba_get_backtrace_from_array (rb_funcall (rb_mKernel, rb_intern ("caller"), 0), bt, 0); + static ID id_caller = rb_intern ("caller"); + rba_get_backtrace_from_array (rb_funcall (rb_mKernel, id_caller, 0), bt, 0); return bt; } @@ -113,7 +114,8 @@ public: // But the purpose is a relative compare, so efficiency is not sacrificed here // for unnecessary consistency. int d = 1; - VALUE backtrace = rb_funcall (rb_mKernel, rb_intern ("caller"), 0); + static ID id_caller = rb_intern ("caller"); + VALUE backtrace = rb_funcall (rb_mKernel, id_caller, 0); if (TYPE (backtrace) == T_ARRAY) { d += RARRAY_LEN(backtrace); } @@ -885,6 +887,15 @@ method_name_from_id (int mid, VALUE self) return cls_decl->name () + "::" + mt->name (mid); } +static gsi::ArgType create_void_type () +{ + gsi::ArgType at; + at.init (); + return at; +} + +static gsi::ArgType s_void_type = create_void_type (); + VALUE method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) { @@ -933,16 +944,18 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) if (p) { + static ID id_set = rb_intern ("set"); + VALUE signal_handler = p->signal_handler (meth); if (rb_block_given_p ()) { VALUE proc = rb_block_proc (); RB_GC_GUARD (proc); - ret = rba_funcall2_checked (signal_handler, rb_intern ("set"), 1, &proc); + ret = rba_funcall2_checked (signal_handler, id_set, 1, &proc); } else if (argc > 0) { - ret = rba_funcall2_checked (signal_handler, rb_intern ("set"), argc, argv); + ret = rba_funcall2_checked (signal_handler, id_set, argc, argv); } else { ret = signal_handler; } @@ -991,6 +1004,24 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) p->set (obj, true, false, true, self); } + } else if (meth->ret_type ().is_iter () && ! rb_block_given_p ()) { + + // calling an iterator method without block -> deliver an enumerator using "to_enum" + + static ID id_to_enum = rb_intern ("to_enum"); + + VALUE method_sym = ID2SYM (rb_intern (meth->primary_name ().c_str ())); + + if (argc == 0) { + ret = rba_funcall2_checked (self, id_to_enum, 1, &method_sym); + } else { + std::vector new_args; + new_args.reserve (size_t (argc + 1)); + new_args.push_back (method_sym); + new_args.insert (new_args.end (), argv, argv + argc); + ret = rba_funcall2_checked (self, id_to_enum, argc + 1, new_args.begin ().operator-> ()); + } + } else { void *obj = 0; @@ -1055,8 +1086,15 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) } + } else if (meth->ret_type () == s_void_type) { + + // simple, yet magical :) + return self; + } else { + ret = pop_arg (meth->ret_type (), p, retlist, heap); + } } diff --git a/testdata/python/dbPolygonTest.py b/testdata/python/dbPolygonTest.py index f98597d07..64cf8e471 100644 --- a/testdata/python/dbPolygonTest.py +++ b/testdata/python/dbPolygonTest.py @@ -728,6 +728,17 @@ class DBPolygonTests(unittest.TestCase): self.assertEqual(str(p1), "(21,42;21,62;41,62;41,42)") self.assertEqual(str(pp), "(21,42;21,62;41,62;41,42)") + def test_voidMethodsReturnSelf(self): + + hull = [ pya.Point(0, 0), pya.Point(6000, 0), + pya.Point(6000, 3000), pya.Point(0, 3000) ] + hole1 = [ pya.Point(1000, 1000), pya.Point(2000, 1000), + pya.Point(2000, 2000), pya.Point(1000, 2000) ] + hole2 = [ pya.Point(3000, 1000), pya.Point(4000, 1000), + pya.Point(4000, 2000), pya.Point(3000, 2000) ] + poly = pya.Polygon(hull).insert_hole(hole1).insert_hole(hole2) + self.assertEqual(str(poly), "(0,0;0,3000;6000,3000;6000,0/1000,1000;2000,1000;2000,2000;1000,2000/3000,1000;4000,1000;4000,2000;3000,2000)") + # run unit tests if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(DBPolygonTests) diff --git a/testdata/ruby/basic_testcore.rb b/testdata/ruby/basic_testcore.rb index 5eee23a67..655a64a8c 100644 --- a/testdata/ruby/basic_testcore.rb +++ b/testdata/ruby/basic_testcore.rb @@ -733,6 +733,8 @@ class Basic_TestClass < TestBase arr = [] b.each_b_copy { |bb| arr.push(bb.b2) } assert_equal(arr, ["a", "y", "uu"]) + # through enumerator + assert_equal(b.each_b_copy.collect(&:b2), ["a", "y", "uu"]) arr = [] b.each_b_copy { |bb| bb.b5(bb.b2 + "x"); arr.push(bb.b2) } @@ -745,6 +747,8 @@ class Basic_TestClass < TestBase arr = [] b.each_b_cref { |bb| arr.push(bb.b2) } assert_equal(arr, ["a", "y", "uu"]) + # through enumerator + assert_equal(b.each_b_cref.collect(&:b2), ["a", "y", "uu"]) arr = [] # this works, since the "const B &" will be converted to a copy @@ -759,6 +763,8 @@ class Basic_TestClass < TestBase arr = [] b.each_b_cptr { |bb| arr.push(bb.b2) } assert_equal(arr, ["a", "y", "uu"]) + # through enumerator + assert_equal(b.each_b_cptr.collect(&:b2), ["a", "y", "uu"]) arr = [] # const references cannot be modified @@ -778,6 +784,8 @@ class Basic_TestClass < TestBase arr = [] b.each_b_ref { |bb| arr.push(bb.b2) } assert_equal(arr, ["a", "y", "uu"]) + # through enumerator + assert_equal(b.each_b_ref.collect(&:b2), ["a", "y", "uu"]) arr = [] b.each_b_ref { |bb| bb.b5(bb.b2 + "x"); arr.push(bb.b2) } @@ -790,6 +798,8 @@ class Basic_TestClass < TestBase arr = [] b.each_b_ptr { |bb| arr.push(bb.b2) } assert_equal(arr, ["ax", "yx", "uux"]) + # through enumerator + assert_equal(b.each_b_ptr.collect(&:b2), ["ax", "yx", "uux"]) arr = [] b.each_b_ptr { |bb| bb.b5(bb.b2 + "x"); arr.push(bb.b2) } diff --git a/testdata/ruby/dbPolygonTest.rb b/testdata/ruby/dbPolygonTest.rb index 0aaec4869..02602f61c 100644 --- a/testdata/ruby/dbPolygonTest.rb +++ b/testdata/ruby/dbPolygonTest.rb @@ -67,6 +67,8 @@ class DBPolygon_TestClass < TestBase arr = [] a.each_point_hull { |p| arr.push( p.to_s ) } assert_equal( arr, ["5,-10", "5,15", "20,15", "20,-10"] ) + # with enumerator + assert_equal( a.each_point_hull.collect(&:to_s), ["5,-10", "5,15", "20,15", "20,-10"] ) b = a.dup @@ -130,6 +132,8 @@ class DBPolygon_TestClass < TestBase arr = [] a.each_point_hole(0) { |p| arr.push( p.to_s ) } assert_equal( arr, ["1,2", "2,2", "2,6"] ) + # with enumerator + assert_equal( a.each_point_hole(0).collect(&:to_s), ["1,2", "2,2", "2,6"] ) arr = [] a.each_edge { |p| arr.push( p.to_s ) } @@ -773,6 +777,19 @@ class DBPolygon_TestClass < TestBase end + def test_voidMethodsReturnSelf + + hull = [ RBA::Point::new(0, 0), RBA::Point::new(6000, 0), + RBA::Point::new(6000, 3000), RBA::Point::new(0, 3000) ] + hole1 = [ RBA::Point::new(1000, 1000), RBA::Point::new(2000, 1000), + RBA::Point::new(2000, 2000), RBA::Point::new(1000, 2000) ] + hole2 = [ RBA::Point::new(3000, 1000), RBA::Point::new(4000, 1000), + RBA::Point::new(4000, 2000), RBA::Point::new(3000, 2000) ] + poly = RBA::Polygon::new(hull).insert_hole(hole1).insert_hole(hole2) + assert_equal(poly.to_s, "(0,0;0,3000;6000,3000;6000,0/1000,1000;2000,1000;2000,2000;1000,2000/3000,1000;4000,1000;4000,2000;3000,2000)") + + end + end load("test_epilogue.rb")