diff --git a/src/db/db/gsiDeclDbTrans.cc b/src/db/db/gsiDeclDbTrans.cc index 24e2f831c..44cb7c523 100644 --- a/src/db/db/gsiDeclDbTrans.cc +++ b/src/db/db/gsiDeclDbTrans.cc @@ -159,7 +159,7 @@ struct trans_defs "@param c The original transformation\n" "@param u The Additional displacement\n" ) + - constructor ("new", &new_cxy, arg ("c"), arg ("x"), arg ("y"), + constructor ("new", &new_cxy, arg ("c"), arg ("x", 0), arg ("y", 0), "@brief Creates a transformation from another transformation plus a displacement\n" "\n" "Creates a new transformation from a existing transformation. This constructor is provided for creating duplicates " @@ -172,7 +172,7 @@ struct trans_defs "@param x The Additional displacement (x)\n" "@param y The Additional displacement (y)\n" ) + - constructor ("new", &new_rmu, arg ("rot"), arg ("mirr", false), arg ("u", displacement_type ()), + constructor ("new", &new_rmu, arg ("rot", 0), arg ("mirrx", false), arg ("u", displacement_type ()), "@brief Creates a transformation using angle and mirror flag\n" "\n" "The sequence of operations is: mirroring at x axis,\n" @@ -182,7 +182,7 @@ struct trans_defs "@param mirrx True, if mirrored at x axis\n" "@param u The displacement\n" ) + - constructor ("new", &new_rmxy, arg ("rot"), arg ("mirr"), arg ("x"), arg ("y"), + constructor ("new", &new_rmxy, arg ("rot", 0), arg ("mirrx", false), arg ("x", 0), arg ("y", 0), "@brief Creates a transformation using angle and mirror flag and two coordinate values for displacement\n" "\n" "The sequence of operations is: mirroring at x axis,\n" @@ -569,7 +569,7 @@ struct cplx_trans_defs return new C (C (u) * C (mag) * c); } - static C *new_cmxy (const C &c, double mag, coord_type x, coord_type y) + static C *new_cmxy (const C &c, double mag, target_coord_type x, target_coord_type y) { return new C (C (displacement_type (x, y)) * C (mag) * c); } @@ -584,29 +584,24 @@ struct cplx_trans_defs return new C (u); } - static C *new_t (const simple_trans_type &t) + static C *new_tm (const simple_trans_type &t, double mag) { - return new C (t, 1.0, 1.0); + return new C (t, 1.0, mag); } - static C *new_tm (const simple_trans_type &t, double m) + static C *new_m (double mag) { - return new C (t, 1.0, m); + return new C (mag); } - static C *new_m (double m) + static C *new_mrmu (double mag, double r, bool mirrx, const displacement_type &u) { - return new C (m); + return new C (mag, r, mirrx, u); } - static C *new_mrmu (double mag, double r, bool m, const displacement_type &u) + static C *new_mrmxy (double mag, double r, bool mirrx, target_coord_type x, target_coord_type y) { - return new C (mag, r, m, u); - } - - static C *new_mrmxy (double mag, double r, bool m, target_coord_type x, target_coord_type y) - { - return new C (mag, r, m, displacement_type (x, y)); + return new C (mag, r, mirrx, displacement_type (x, y)); } static simple_trans_type s_trans (const C *cplx_trans) @@ -650,7 +645,7 @@ struct cplx_trans_defs constructor ("new", &new_v, "@brief Creates a unit transformation\n" ) + - constructor ("new", &new_cmu, arg ("c"), arg ("m", 1.0), arg ("u", displacement_type ()), + constructor ("new", &new_cmu, arg ("c"), arg ("mag", 1.0), arg ("u", displacement_type ()), "@brief Creates a transformation from another transformation plus a magnification and displacement\n" "\n" "Creates a new transformation from a existing transformation. This constructor is provided for creating duplicates " @@ -662,7 +657,7 @@ struct cplx_trans_defs "@param c The original transformation\n" "@param u The Additional displacement\n" ) + - constructor ("new", &new_cmxy, arg ("c"), arg ("m"), arg ("x"), arg ("y"), + constructor ("new", &new_cmxy, arg ("c"), arg ("mag", 1.0), arg ("x", 0), arg ("y", 0), "@brief Creates a transformation from another transformation plus a magnification and displacement\n" "\n" "Creates a new transformation from a existing transformation. This constructor is provided for creating duplicates " @@ -684,21 +679,11 @@ struct cplx_trans_defs "@param x The x displacement\n" "@param y The y displacement\n" ) + - constructor ("new", &new_m, arg ("m"), - "@brief Creates a transformation from a magnification\n" - "\n" - "Creates a magnifying transformation without displacement and rotation given the magnification m." - ) + - constructor ("new", &new_tm, arg ("t"), arg ("m"), + constructor ("new", &new_tm, arg ("t"), arg ("mag", 1.0), "@brief Creates a transformation from a simple transformation and a magnification\n" "\n" "Creates a magnifying transformation from a simple transformation and a magnification." ) + - constructor ("new", &new_t, arg ("t"), - "@brief Creates a transformation from a simple transformation alone\n" - "\n" - "Creates a magnifying transformation from a simple transformation and a magnification of 1.0." - ) + constructor ("new", &new_u, arg ("u"), "@brief Creates a transformation from a displacement\n" "\n" @@ -706,7 +691,7 @@ struct cplx_trans_defs "\n" "This method has been added in version 0.25." ) + - constructor ("new", &new_mrmu, arg ("mag"), arg ("rot"), arg ("mirrx"), arg ("u"), + constructor ("new", &new_mrmu, arg ("mag", 1.0), arg ("rot", 0.0), arg ("mirrx", false), arg ("u", displacement_type ()), "@brief Creates a transformation using magnification, angle, mirror flag and displacement\n" "\n" "The sequence of operations is: magnification, mirroring at x axis,\n" @@ -717,7 +702,7 @@ struct cplx_trans_defs "@param mirrx True, if mirrored at x axis\n" "@param u The displacement\n" ) + - constructor ("new", &new_mrmxy, arg ("mag"), arg ("rot"), arg ("mirrx"), arg ("x"), arg ("y"), + constructor ("new", &new_mrmxy, arg ("mag", 1.0), arg ("rot", 0.0), arg ("mirrx", false), arg ("x", 0), arg ("y", 0), "@brief Creates a transformation using magnification, angle, mirror flag and displacement\n" "\n" "The sequence of operations is: magnification, mirroring at x axis,\n" diff --git a/src/gsi/gsi/gsi.pro b/src/gsi/gsi/gsi.pro index 83cf1217b..57686e448 100644 --- a/src/gsi/gsi/gsi.pro +++ b/src/gsi/gsi/gsi.pro @@ -22,6 +22,7 @@ SOURCES = \ gsiTypes.cc \ gsiSignals.cc \ gsiObjectHolder.cc \ + gsiVariantArgs.cc HEADERS = \ gsiCallback.h \ @@ -43,7 +44,8 @@ HEADERS = \ gsiSignals.h \ gsiTypes.h \ gsiObjectHolder.h \ - gsiCommon.h + gsiCommon.h \ + gsiVariantArgs.h # Note: unlike other modules, the tl declarations have to go here # since gsi is dependent on tl diff --git a/src/gsi/gsi/gsiExpression.cc b/src/gsi/gsi/gsiExpression.cc index 11045a6df..2c2980c6e 100644 --- a/src/gsi/gsi/gsiExpression.cc +++ b/src/gsi/gsi/gsiExpression.cc @@ -24,6 +24,7 @@ #include "gsiDecl.h" #include "gsiExpression.h" #include "gsiObjectHolder.h" +#include "gsiVariantArgs.h" #include "tlExpression.h" #include "tlLog.h" @@ -252,874 +253,6 @@ void *get_object_raw (tl::Variant &var) return obj; } -// ------------------------------------------------------------------- -// Test if an argument can be converted to the given type - -bool test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose); - -template -struct test_arg_func -{ - void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType & /*atype*/, bool /*loose*/) - { - *ret = arg.can_convert_to (); - } -}; - -template <> -struct test_arg_func -{ - void operator () (bool *ret, const tl::Variant & /*arg*/, const gsi::ArgType & /*atype*/, bool /*loose*/) - { - *ret = true; - } -}; - -template <> -struct test_arg_func -{ - void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose) - { - // allow nil of pointers - if ((atype.is_ptr () || atype.is_cptr ()) && arg.is_nil ()) { - *ret = true; - return; - } - - if (arg.is_list ()) { - - // we may implicitly convert an array into a constructor call of a target object - - // for now we only check whether the number of arguments is compatible with the array given. - - int n = int (arg.size ()); - - *ret = false; - for (gsi::ClassBase::method_iterator c = atype.cls ()->begin_constructors (); c != atype.cls ()->end_constructors (); ++c) { - if ((*c)->compatible_with_num_args (n)) { - *ret = true; - break; - } - } - - return; - - } - - if (! arg.is_user ()) { - *ret = false; - return; - } - - const tl::VariantUserClassBase *cls = arg.user_cls (); - if (! cls) { - *ret = false; - } else if (! cls->gsi_cls ()->is_derived_from (atype.cls ()) && (! loose || ! cls->gsi_cls ()->can_convert_to(atype.cls ()))) { - *ret = false; - } else if ((atype.is_ref () || atype.is_ptr ()) && cls->is_const ()) { - *ret = false; - } else { - *ret = true; - } - } -}; - -template <> -struct test_arg_func -{ - void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose) - { - if (! arg.is_list ()) { - *ret = false; - return; - } - - tl_assert (atype.inner () != 0); - const ArgType &ainner = *atype.inner (); - - *ret = true; - for (tl::Variant::const_iterator v = arg.begin (); v != arg.end () && *ret; ++v) { - if (! test_arg (ainner, *v, loose)) { - *ret = false; - } - } - } -}; - -template <> -struct test_arg_func -{ - void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose) - { - // Note: delegating that to the function avoids "injected class name used as template template expression" warning - if (! arg.is_array ()) { - *ret = false; - return; - } - - tl_assert (atype.inner () != 0); - tl_assert (atype.inner_k () != 0); - const ArgType &ainner = *atype.inner (); - const ArgType &ainner_k = *atype.inner_k (); - - if (! arg.is_list ()) { - *ret = false; - return; - } - - *ret = true; - for (tl::Variant::const_array_iterator a = arg.begin_array (); a != arg.end_array () && *ret; ++a) { - if (! test_arg (ainner_k, a->first, loose)) { - *ret = false; - } else if (! test_arg (ainner, a->second, loose)) { - *ret = false; - } - } - } -}; - -bool -test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose) -{ - // for const X * or X *, nil is an allowed value - if ((atype.is_cptr () || atype.is_ptr ()) && arg.is_nil ()) { - return true; - } - - bool ret = false; - gsi::do_on_type () (atype.type (), &ret, arg, atype, loose); - return ret; -} - -// ------------------------------------------------------------------- -// Variant to C conversion - -template -struct var2c -{ - static R get (const tl::Variant &rval) - { - return rval.to (); - } -}; - -template <> -struct var2c -{ - static const tl::Variant &get (const tl::Variant &rval) - { - return rval; - } -}; - -// --------------------------------------------------------------------- -// Serialization helpers - -/** - * @brief An adaptor for a vector which uses the tl::Variant's list perspective - */ -class VariantBasedVectorAdaptorIterator - : public gsi::VectorAdaptorIterator -{ -public: - VariantBasedVectorAdaptorIterator (tl::Variant::iterator b, tl::Variant::iterator e, const gsi::ArgType *ainner); - - virtual void get (SerialArgs &w, tl::Heap &heap) const; - virtual bool at_end () const; - virtual void inc (); - -private: - tl::Variant::iterator m_b, m_e; - const gsi::ArgType *mp_ainner; -}; - -/** - * @brief An adaptor for a vector which uses the tl::Variant's list perspective - */ -class VariantBasedVectorAdaptor - : public gsi::VectorAdaptor -{ -public: - VariantBasedVectorAdaptor (tl::Variant *var, const gsi::ArgType *ainner); - - virtual VectorAdaptorIterator *create_iterator () const; - virtual void push (SerialArgs &r, tl::Heap &heap); - virtual void clear (); - virtual size_t size () const; - virtual size_t serial_size () const; - -private: - const gsi::ArgType *mp_ainner; - tl::Variant *mp_var; -}; - -/** - * @brief An adaptor for a map which uses the tl::Variant's array perspective - */ -class VariantBasedMapAdaptorIterator - : public gsi::MapAdaptorIterator -{ -public: - VariantBasedMapAdaptorIterator (tl::Variant::array_iterator b, tl::Variant::array_iterator e, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k); - - virtual void get (SerialArgs &w, tl::Heap &heap) const; - virtual bool at_end () const; - virtual void inc (); - -private: - tl::Variant::array_iterator m_b, m_e; - const gsi::ArgType *mp_ainner, *mp_ainner_k; -}; - -/** - * @brief An adaptor for a vector which uses the tl::Variant's list perspective - */ -class VariantBasedMapAdaptor - : public gsi::MapAdaptor -{ -public: - VariantBasedMapAdaptor (tl::Variant *var, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k); - - virtual MapAdaptorIterator *create_iterator () const; - virtual void insert (SerialArgs &r, tl::Heap &heap); - virtual void clear (); - virtual size_t size () const; - virtual size_t serial_size () const; - -private: - const gsi::ArgType *mp_ainner, *mp_ainner_k; - tl::Variant *mp_var; -}; - -// --------------------------------------------------------------------- -// Writer function for serialization - -/** - * @brief Serialization of POD types - */ -template -struct writer -{ - void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *heap) - { - if (arg->is_nil () && atype.type () != gsi::T_var) { - - if (! (atype.is_ptr () || atype.is_cptr ())) { - throw tl::Exception (tl::to_string (tr ("Arguments of reference or direct type cannot be passed nil"))); - } else if (atype.is_ptr ()) { - aa->write ((R *)0); - } else { - aa->write ((const R *)0); - } - - } else { - - if (atype.is_ref () || atype.is_ptr ()) { - - // TODO: morph the variant to the requested type and pass its pointer (requires a non-const reference for arg) - // -> we would have a reference that can modify the argument (out parameter). - R *v = new R (var2c::get (*arg)); - heap->push (v); - - aa->write (v); - - } else if (atype.is_cref ()) { - // Note: POD's are written as copies for const refs, so we can pass a temporary here: - // (avoids having to create a temp object) - aa->write (var2c::get (*arg)); - } else if (atype.is_cptr ()) { - // Note: POD's are written as copies for const ptrs, so we can pass a temporary here: - // (avoids having to create a temp object) - R r = var2c::get (*arg); - aa->write (&r); - } else { - aa->write (var2c::get (*arg)); - } - - } - } -}; - -/** - * @brief Serialization for strings - */ -template <> -struct writer -{ - void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *) - { - // Cannot pass ownership currently - tl_assert (!atype.pass_obj ()); - - if (arg->is_nil ()) { - - if (! (atype.is_ptr () || atype.is_cptr ())) { - // nil is treated as an empty string for references - aa->write ((void *)new StringAdaptorImpl (std::string ())); - } else { - aa->write ((void *)0); - } - - } else { - - // TODO: morph the variant to the requested type and pass its pointer (requires a non-const reference for arg) - // -> we would have a reference that can modify the argument (out parameter). - // NOTE: by convention we pass the ownership to the receiver for adaptors. - aa->write ((void *)new StringAdaptorImpl (arg->to_string ())); - - } - } -}; - -/** - * @brief Specialization for Variant - */ -template <> -struct writer -{ - void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &, tl::Heap *) - { - // TODO: clarify: is nil a zero-pointer to a variant or a pointer to a "nil" variant? - // NOTE: by convention we pass the ownership to the receiver for adaptors. - aa->write ((void *)new VariantAdaptorImpl (arg)); - } -}; - -/** - * @brief Specialization for Vectors - */ -template <> -struct writer -{ - void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *) - { - if (arg->is_nil ()) { - if (! (atype.is_ptr () || atype.is_cptr ())) { - throw tl::Exception (tl::to_string (tr ("Arguments of reference or direct type cannot be passed nil"))); - } else { - aa->write ((void *)0); - } - } else { - tl_assert (atype.inner () != 0); - aa->write ((void *)new VariantBasedVectorAdaptor (arg, atype.inner ())); - } - } -}; - -/** - * @brief Specialization for Maps - */ -template <> -struct writer -{ - void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *) - { - if (arg->is_nil ()) { - if (! (atype.is_ptr () || atype.is_cptr ())) { - throw tl::Exception (tl::to_string (tr ("Arguments of reference or direct type cannot be passed nil"))); - } else { - aa->write ((void *)0); - } - } else { - tl_assert (atype.inner () != 0); - tl_assert (atype.inner_k () != 0); - aa->write ((void *)new VariantBasedMapAdaptor (arg, atype.inner (), atype.inner_k ())); - } - } -}; - -/** - * @brief Specialization for void - */ -template <> -struct writer -{ - void operator() (gsi::SerialArgs *, tl::Variant *, const gsi::ArgType &, tl::Heap *) - { - // nothing - void type won't be serialized - } -}; - -void push_args (gsi::SerialArgs &arglist, const tl::Variant &args, const gsi::MethodBase *meth, tl::Heap *heap); - -/** - * @brief Specialization for void - */ -template <> -struct writer -{ - void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *heap) - { - if (arg->is_nil ()) { - - if (atype.is_ref () || atype.is_cref ()) { - throw tl::Exception (tl::to_string (tr ("Cannot pass nil to reference parameters"))); - } else if (! atype.is_cptr () && ! atype.is_ptr ()) { - throw tl::Exception (tl::to_string (tr ("Cannot pass nil to direct parameters"))); - } - - aa->write ((void *) 0); - - } else if (arg->is_list ()) { - - // we may implicitly convert an array into a constructor call of a target object - - // for now we only check whether the number of arguments is compatible with the array given. - - int n = int (arg->size ()); - const gsi::MethodBase *meth = 0; - for (gsi::ClassBase::method_iterator c = atype.cls ()->begin_constructors (); c != atype.cls ()->end_constructors (); ++c) { - if ((*c)->compatible_with_num_args (n)) { - meth = *c; - break; - } - } - - if (!meth) { - throw tl::Exception (tl::to_string (tr ("No constructor of %s available that takes %d arguments (implicit call from tuple)")), atype.cls ()->name (), n); - } - - // implicit call of constructor - gsi::SerialArgs retlist (meth->retsize ()); - gsi::SerialArgs arglist (meth->argsize ()); - - push_args (arglist, *arg, meth, heap); - - meth->call (0, arglist, retlist); - - void *new_obj = retlist.read (*heap); - if (new_obj && (atype.is_ptr () || atype.is_cptr () || atype.is_ref () || atype.is_cref ())) { - // For pointers or refs, ownership over these objects is not transferred. - // Hence we have to keep them on the heap. - // TODO: what if the called method takes ownership using keep()? - heap->push (new gsi::ObjectHolder (atype.cls (), new_obj)); - } - - aa->write (new_obj); - - } else { - - if (! arg->is_user ()) { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s)")), atype.cls ()->name ())); - } - - const tl::VariantUserClassBase *cls = arg->user_cls (); - if (!cls) { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s)")), atype.cls ()->name ())); - } - if (cls->is_const () && (atype.is_ref () || atype.is_ptr ())) { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot pass a const reference of class %s to a non-const reference or pointer parameter")), atype.cls ()->name ())); - } - - if (atype.is_ref () || atype.is_cref () || atype.is_ptr () || atype.is_cptr ()) { - - if (cls->gsi_cls ()->is_derived_from (atype.cls ())) { - - if (cls->gsi_cls ()->adapted_type_info ()) { - // resolved adapted type - aa->write ((void *) cls->gsi_cls ()->adapted_from_obj (get_object (*arg))); - } else { - aa->write (get_object (*arg)); - } - - } else if ((atype.is_cref () || atype.is_cptr ()) && cls->gsi_cls ()->can_convert_to (atype.cls ())) { - - // We can convert objects for cref and cptr, but ownership over these objects is not transferred. - // Hence we have to keep them on the heap. - void *new_obj = atype.cls ()->create_obj_from (cls->gsi_cls (), get_object (*arg)); - heap->push (new gsi::ObjectHolder (atype.cls (), new_obj)); - aa->write (new_obj); - - } else { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s)")), atype.cls ()->name ())); - } - - } else { - - if (cls->gsi_cls ()->is_derived_from (atype.cls ())) { - - if (cls->gsi_cls ()->adapted_type_info ()) { - aa->write (cls->gsi_cls ()->create_adapted_from_obj (get_object (*arg))); - } else { - aa->write ((void *) cls->gsi_cls ()->clone (get_object (*arg))); - } - - } else if (cls->gsi_cls ()->can_convert_to (atype.cls ())) { - - aa->write (atype.cls ()->create_obj_from (cls->gsi_cls (), get_object (*arg))); - - } else { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s)")), atype.cls ()->name ())); - } - - } - - } - - } -}; - -void push_args (gsi::SerialArgs &arglist, const tl::Variant &args, const gsi::MethodBase *meth, tl::Heap *heap) -{ - int n = int (args.size ()); - int narg = 0; - - for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && narg < n; ++a, ++narg) { - try { - // Note: this const_cast is ugly, but it will basically enable "out" parameters - // TODO: clean this up. - gsi::do_on_type () (a->type (), &arglist, const_cast ((args.get_list ().begin () + narg).operator-> ()), *a, heap); - } catch (tl::Exception &ex) { - std::string msg = ex.msg () + tl::sprintf (tl::to_string (tr (" (argument '%s')")), a->spec ()->name ()); - throw tl::Exception (msg); - } - } -} - -// --------------------------------------------------------------------- -// Reader function for serialization - -/** - * @brief A reader function - */ -template -struct reader -{ - void - operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) - { - if (atype.is_ref ()) { - *out = rr->template read (*heap); - } else if (atype.is_cref ()) { - *out = rr->template read (*heap); - } else if (atype.is_ptr ()) { - R *p = rr->template read (*heap); - if (p == 0) { - *out = tl::Variant (); - } else { - *out = *p; - } - } else if (atype.is_cptr ()) { - const R *p = rr->template read (*heap); - if (p == 0) { - *out = tl::Variant (); - } else { - *out = *p; - } - } else { - *out = rr->template read (*heap); - } - } -}; - -/** - * @brief A reader specialization for void * - */ -template <> -struct reader -{ - void - operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) - { - tl_assert (!atype.is_ref ()); - tl_assert (!atype.is_cref ()); - tl_assert (!atype.is_ptr ()); - tl_assert (!atype.is_cptr ()); - *out = size_t (rr->read (*heap)); - } -}; - -/** - * @brief A reader specialization for strings - */ -template <> -struct reader -{ - void - operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &, tl::Heap *heap) - { - std::unique_ptr a ((StringAdaptor *) rr->read(*heap)); - if (!a.get ()) { - *out = tl::Variant (); - } else { - *out = tl::Variant (std::string (a->c_str (), a->size ())); - } - } -}; - -/** - * @brief A reader specialization for variants - */ -template <> -struct reader -{ - void - operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &, tl::Heap *heap) - { - std::unique_ptr a ((VariantAdaptor *) rr->read(*heap)); - if (!a.get ()) { - *out = tl::Variant (); - } else { - *out = a->var (); - } - } -}; - -/** - * @brief A reader specialization for maps - */ -template <> -struct reader -{ - void - operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) - { - std::unique_ptr a ((MapAdaptor *) rr->read(*heap)); - if (!a.get ()) { - *out = tl::Variant (); - } else { - tl_assert (atype.inner () != 0); - tl_assert (atype.inner_k () != 0); - VariantBasedMapAdaptor t (out, atype.inner (), atype.inner_k ()); - a->copy_to (&t, *heap); - } - } -}; - -/** - * @brief A reader specialization for const char * - */ -template <> -struct reader -{ - void - operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) - { - std::unique_ptr a ((VectorAdaptor *) rr->read(*heap)); - if (!a.get ()) { - *out = tl::Variant (); - } else { - tl_assert (atype.inner () != 0); - VariantBasedVectorAdaptor t (out, atype.inner ()); - a->copy_to (&t, *heap); - } - } -}; - -/** - * @brief A reader specialization for objects - */ -template <> -struct reader -{ - void - operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) - { - void *obj = rr->read (*heap); - - bool is_const = atype.is_cptr () || atype.is_cref (); - bool owner = true; - if (atype.is_ptr () || atype.is_cptr () || atype.is_ref () || atype.is_cref ()) { - owner = atype.pass_obj (); - } - bool can_destroy = atype.is_ptr () || owner; - - const gsi::ClassBase *clsact = atype.cls ()->subclass_decl (obj); - tl_assert (clsact != 0); - - if (obj == 0) { - - *out = tl::Variant (); - - } else if (!clsact->adapted_type_info () && clsact->is_managed ()) { - - // gsi::ObjectBase-based objects can be managed by reference since they - // provide a tl::Object through the proxy. - - *out = tl::Variant (); - - const tl::VariantUserClassBase *cls = clsact->var_cls (atype.is_cref () || atype.is_cptr ()); - tl_assert (cls != 0); - - Proxy *proxy = clsact->gsi_object (obj)->find_client (); - if (proxy) { - - out->set_user_ref (proxy, cls, false); - - } else { - - // establish a new proxy - proxy = new Proxy (clsact); - proxy->set (obj, owner, is_const, can_destroy); - - out->set_user_ref (proxy, cls, owner); - - } - - } else { - - const tl::VariantUserClassBase *cls = 0; - - if (clsact->adapted_type_info ()) { - // create an adaptor from an adapted type - if (owner) { - obj = clsact->create_from_adapted_consume (obj); - } else { - obj = clsact->create_from_adapted (obj); - } - cls = clsact->var_cls (false); - } else { - cls = clsact->var_cls (is_const); - } - - tl_assert (cls != 0); - *out = tl::Variant (); - - // consider prefer_copy - if (! owner && atype.prefer_copy () && !clsact->is_managed () && clsact->can_copy ()) { - obj = clsact->clone (obj); - owner = true; - } - - out->set_user (obj, cls, owner); - - } - } -}; - -/** - * @brief A reader specialization for new objects - */ -template <> -struct reader -{ - void - operator() (tl::Variant *, gsi::SerialArgs *, const gsi::ArgType &, tl::Heap *) - { - // nothing - void type won't be serialized - } -}; - -// --------------------------------------------------------------------- -// VariantBasedVectorAdaptorIterator implementation - -VariantBasedVectorAdaptorIterator::VariantBasedVectorAdaptorIterator (tl::Variant::iterator b, tl::Variant::iterator e, const gsi::ArgType *ainner) - : m_b (b), m_e (e), mp_ainner (ainner) -{ - // .. nothing yet .. -} - -void VariantBasedVectorAdaptorIterator::get (SerialArgs &w, tl::Heap &heap) const -{ - gsi::do_on_type () (mp_ainner->type (), &w, &*m_b, *mp_ainner, &heap); -} - -bool VariantBasedVectorAdaptorIterator::at_end () const -{ - return m_b == m_e; -} - -void VariantBasedVectorAdaptorIterator::inc () -{ - ++m_b; -} - -// --------------------------------------------------------------------- -// VariantBasedVectorAdaptor implementation - -VariantBasedVectorAdaptor::VariantBasedVectorAdaptor (tl::Variant *var, const gsi::ArgType *ainner) - : mp_ainner (ainner), mp_var (var) -{ -} - -VectorAdaptorIterator *VariantBasedVectorAdaptor::create_iterator () const -{ - return new VariantBasedVectorAdaptorIterator (mp_var->begin (), mp_var->end (), mp_ainner); -} - -void VariantBasedVectorAdaptor::push (SerialArgs &r, tl::Heap &heap) -{ - tl::Variant member; - gsi::do_on_type () (mp_ainner->type (), &member, &r, *mp_ainner, &heap); - mp_var->push (member); -} - -void VariantBasedVectorAdaptor::clear () -{ - mp_var->set_list (); -} - -size_t VariantBasedVectorAdaptor::size () const -{ - return mp_var->size (); -} - -size_t VariantBasedVectorAdaptor::serial_size () const -{ - return mp_ainner->size (); -} - -// --------------------------------------------------------------------- -// VariantBasedMapAdaptorIterator implementation - -VariantBasedMapAdaptorIterator::VariantBasedMapAdaptorIterator (tl::Variant::array_iterator b, tl::Variant::array_iterator e, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k) - : m_b (b), m_e (e), mp_ainner (ainner), mp_ainner_k (ainner_k) -{ - // .. nothing yet .. -} - -void VariantBasedMapAdaptorIterator::get (SerialArgs &w, tl::Heap &heap) const -{ - // Note: the const_cast is ugly but in this context we won't modify the variant given as the key. - // And it lets us keep the interface tidy. - gsi::do_on_type () (mp_ainner_k->type (), &w, const_cast (&m_b->first), *mp_ainner_k, &heap); - gsi::do_on_type () (mp_ainner->type (), &w, &m_b->second, *mp_ainner, &heap); -} - -bool VariantBasedMapAdaptorIterator::at_end () const -{ - return m_b == m_e; -} - -void VariantBasedMapAdaptorIterator::inc () -{ - ++m_b; -} - -// --------------------------------------------------------------------- -// VariantBasedMapAdaptor implementation - -VariantBasedMapAdaptor::VariantBasedMapAdaptor (tl::Variant *var, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k) - : mp_ainner (ainner), mp_ainner_k (ainner_k), mp_var (var) -{ -} - -MapAdaptorIterator *VariantBasedMapAdaptor::create_iterator () const -{ - return new VariantBasedMapAdaptorIterator (mp_var->begin_array (), mp_var->end_array (), mp_ainner, mp_ainner_k); -} - -void VariantBasedMapAdaptor::insert (SerialArgs &r, tl::Heap &heap) -{ - tl::Variant k, v; - gsi::do_on_type () (mp_ainner_k->type (), &k, &r, *mp_ainner_k, &heap); - gsi::do_on_type () (mp_ainner->type (), &v, &r, *mp_ainner, &heap); - mp_var->insert (k, v); -} - -void VariantBasedMapAdaptor::clear () -{ - mp_var->set_array (); -} - -size_t VariantBasedMapAdaptor::size () const -{ - return mp_var->array_size (); -} - -size_t VariantBasedMapAdaptor::serial_size () const -{ - return mp_ainner_k->size () + mp_ainner->size (); -} - // --------------------------------------------------------------------- // Implementation of initialize_expressions @@ -1671,7 +804,7 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context int sc = 0; int i = 0; for (gsi::MethodBase::argument_iterator a = (*m)->begin_arguments (); is_valid && i < int (args.size ()) && a != (*m)->end_arguments (); ++a, ++i) { - if (test_arg (*a, args [i], false /*strict*/)) { + if (gsi::test_arg (*a, args [i], false /*strict*/)) { ++sc; } else if (test_arg (*a, args [i], true /*loose*/)) { // non-scoring match @@ -1747,7 +880,7 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context try { // Note: this const_cast is ugly, but it will basically enable "out" parameters // TODO: clean this up. - gsi::do_on_type () (a->type (), &arglist, const_cast (&args [narg]), *a, &heap); + gsi::push_arg (arglist, *a, const_cast (args [narg]), &heap); } catch (tl::Exception &ex) { std::string msg = ex.msg () + tl::sprintf (tl::to_string (tr (" (argument '%s')")), a->spec ()->name ()); throw tl::Exception (msg); @@ -1764,7 +897,7 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context } else { out = tl::Variant (); try { - gsi::do_on_type () (meth->ret_type ().type (), &out, &retlist, meth->ret_type (), &heap); + gsi::pull_arg (retlist, meth->ret_type (), out, &heap); } catch (tl::Exception &ex) { std::string msg = ex.msg () + tl::to_string (tr (" (return value)")); throw tl::Exception (msg); diff --git a/src/gsi/gsi/gsiMethods.cc b/src/gsi/gsi/gsiMethods.cc index 9f6100b22..717ce0389 100644 --- a/src/gsi/gsi/gsiMethods.cc +++ b/src/gsi/gsi/gsiMethods.cc @@ -132,16 +132,132 @@ void MethodBase::parse_name (const std::string &name) } } +static std::string +type_to_s (const gsi::ArgType &a, bool for_return) +{ + std::string s; + switch (a.type ()) { + case gsi::T_void_ptr: + s += "void *"; break; + case gsi::T_void: + s += "void"; break; + case gsi::T_bool: + s += "bool"; break; + case gsi::T_char: + s += "char"; break; + case gsi::T_schar: + s += "signed char"; break; + case gsi::T_uchar: + s += "unsigned char"; break; + case gsi::T_short: + s += "short"; break; + case gsi::T_ushort: + s += "unsigned short"; break; + case gsi::T_int: + s += "int"; break; +#if defined(HAVE_64BIT_COORD) + case gsi::T_int128: + s += "int128"; break; +#endif + case gsi::T_uint: + s += "unsigned int"; break; + case gsi::T_long: + s += "long"; break; + case gsi::T_ulong: + s += "unsigned long"; break; + case gsi::T_longlong: + s += "long long"; break; + case gsi::T_ulonglong: + s += "unsigned long long"; break; + case gsi::T_double: + s += "double"; break; + case gsi::T_float: + s += "float"; break; + case gsi::T_string: + s += "string"; break; + case gsi::T_byte_array: + s += "bytes"; break; + case gsi::T_var: + s += "variant"; break; + case gsi::T_object: + if (a.is_cptr () || (! for_return && a.is_cref ())) { + s = "const "; + } + if (a.pass_obj ()) { + s += "new "; + } + s += a.cls () ? a.cls ()->qname () : "?"; + break; + case gsi::T_vector: + if (a.inner ()) { + s += type_to_s (*a.inner (), false); + } + s += "[]"; + break; + case gsi::T_map: + s += "map<"; + if (a.inner_k ()) { + s += type_to_s (*a.inner_k (), false); + } + s += ","; + if (a.inner ()) { + s += type_to_s (*a.inner (), false); + } + s += ">"; + break; + } + if (a.is_cptr () || a.is_ptr ()) { + s += " ptr"; + } + return s; +} + +static std::string +method_attributes (const gsi::MethodBase *method) +{ + std::string r; + if (method->is_signal ()) { + if (! r.empty ()) { + r += ","; + } + r += "signal"; + } + if (method->is_callback ()) { + if (! r.empty ()) { + r += ","; + } + r += "virtual"; + } + if (method->is_static ()) { + if (! r.empty ()) { + r += ","; + } + r += "static"; + } + if (method->is_const ()) { + if (! r.empty ()) { + r += ","; + } + r += "const"; + } + if (method->ret_type ().is_iter ()) { + if (! r.empty ()) { + r += ","; + } + r += "iter"; + } + return r; +} + std::string MethodBase::to_string () const { - std::string res; - - if (is_static ()) { - res += "static "; + std::string res = method_attributes (this); + if (! res.empty ()) { + res += " "; } - res += ret_type ().to_string (); + res += type_to_s (ret_type (), true); res += " "; if (m_method_synonyms.size () == 1) { @@ -155,7 +271,24 @@ MethodBase::to_string () const if (a != begin_arguments ()) { res += ", "; } - res += a->to_string (); + res += type_to_s (*a, false); + if (! a->spec ()->name ().empty ()) { + res += " "; + res += a->spec ()->name (); + } + if (a->spec ()->has_default ()) { + res += " = "; + if (! a->spec ()->init_doc ().empty ()) { + res += a->spec ()->init_doc (); + } else { + try { + res += a->spec ()->default_value ().to_string (); + } catch (tl::Exception &) { + res += "?"; + } + } + + } } res += ")"; diff --git a/src/gsi/gsi/gsiVariantArgs.cc b/src/gsi/gsi/gsiVariantArgs.cc new file mode 100644 index 000000000..7a5d8b038 --- /dev/null +++ b/src/gsi/gsi/gsiVariantArgs.cc @@ -0,0 +1,928 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2023 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "gsiVariantArgs.h" +#include "gsiTypes.h" +#include "gsiSerialisation.h" +#include "gsiClassBase.h" +#include "gsiObjectHolder.h" + +#include "tlVariant.h" +#include "tlHeap.h" + +namespace gsi +{ + +// ------------------------------------------------------------------- + +/** + * @brief Fetches the final object pointer from a tl::Variant + */ +inline void *get_object (tl::Variant &var) +{ + return var.to_user (); +} + +// ------------------------------------------------------------------- +// Test if an argument can be converted to the given type + +bool test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose); + +template +struct test_arg_func +{ + void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType & /*atype*/, bool /*loose*/) + { + *ret = arg.can_convert_to (); + } +}; + +template <> +struct test_arg_func +{ + void operator () (bool *ret, const tl::Variant & /*arg*/, const gsi::ArgType & /*atype*/, bool /*loose*/) + { + *ret = true; + } +}; + +template <> +struct test_arg_func +{ + void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose) + { + // allow nil of pointers + if ((atype.is_ptr () || atype.is_cptr ()) && arg.is_nil ()) { + *ret = true; + return; + } + + if (arg.is_list ()) { + + // we may implicitly convert an array into a constructor call of a target object - + // for now we only check whether the number of arguments is compatible with the array given. + + int n = int (arg.size ()); + + *ret = false; + for (gsi::ClassBase::method_iterator c = atype.cls ()->begin_constructors (); c != atype.cls ()->end_constructors (); ++c) { + if ((*c)->compatible_with_num_args (n)) { + *ret = true; + break; + } + } + + return; + + } + + if (! arg.is_user ()) { + *ret = false; + return; + } + + const tl::VariantUserClassBase *cls = arg.user_cls (); + if (! cls) { + *ret = false; + } else if (! cls->gsi_cls ()->is_derived_from (atype.cls ()) && (! loose || ! cls->gsi_cls ()->can_convert_to(atype.cls ()))) { + *ret = false; + } else if ((atype.is_ref () || atype.is_ptr ()) && cls->is_const ()) { + *ret = false; + } else { + *ret = true; + } + } +}; + +template <> +struct test_arg_func +{ + void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose) + { + if (! arg.is_list ()) { + *ret = false; + return; + } + + tl_assert (atype.inner () != 0); + const ArgType &ainner = *atype.inner (); + + *ret = true; + for (tl::Variant::const_iterator v = arg.begin (); v != arg.end () && *ret; ++v) { + if (! test_arg (ainner, *v, loose)) { + *ret = false; + } + } + } +}; + +template <> +struct test_arg_func +{ + void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose) + { + // Note: delegating that to the function avoids "injected class name used as template template expression" warning + if (! arg.is_array ()) { + *ret = false; + return; + } + + tl_assert (atype.inner () != 0); + tl_assert (atype.inner_k () != 0); + const ArgType &ainner = *atype.inner (); + const ArgType &ainner_k = *atype.inner_k (); + + if (! arg.is_list ()) { + *ret = false; + return; + } + + *ret = true; + for (tl::Variant::const_array_iterator a = arg.begin_array (); a != arg.end_array () && *ret; ++a) { + if (! test_arg (ainner_k, a->first, loose)) { + *ret = false; + } else if (! test_arg (ainner, a->second, loose)) { + *ret = false; + } + } + } +}; + +bool +test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose) +{ + // for const X * or X *, nil is an allowed value + if ((atype.is_cptr () || atype.is_ptr ()) && arg.is_nil ()) { + return true; + } + + bool ret = false; + gsi::do_on_type () (atype.type (), &ret, arg, atype, loose); + return ret; +} + +// ------------------------------------------------------------------- +// Variant to C conversion + +template +struct var2c +{ + static R get (const tl::Variant &rval) + { + return rval.to (); + } +}; + +template <> +struct var2c +{ + static const tl::Variant &get (const tl::Variant &rval) + { + return rval; + } +}; + +// --------------------------------------------------------------------- +// Serialization helpers + +/** + * @brief An adaptor for a vector which uses the tl::Variant's list perspective + */ +class VariantBasedVectorAdaptorIterator + : public gsi::VectorAdaptorIterator +{ +public: + VariantBasedVectorAdaptorIterator (tl::Variant::iterator b, tl::Variant::iterator e, const gsi::ArgType *ainner); + + virtual void get (SerialArgs &w, tl::Heap &heap) const; + virtual bool at_end () const; + virtual void inc (); + +private: + tl::Variant::iterator m_b, m_e; + const gsi::ArgType *mp_ainner; +}; + +/** + * @brief An adaptor for a vector which uses the tl::Variant's list perspective + */ +class VariantBasedVectorAdaptor + : public gsi::VectorAdaptor +{ +public: + VariantBasedVectorAdaptor (tl::Variant *var, const gsi::ArgType *ainner); + + virtual VectorAdaptorIterator *create_iterator () const; + virtual void push (SerialArgs &r, tl::Heap &heap); + virtual void clear (); + virtual size_t size () const; + virtual size_t serial_size () const; + +private: + const gsi::ArgType *mp_ainner; + tl::Variant *mp_var; +}; + +/** + * @brief An adaptor for a map which uses the tl::Variant's array perspective + */ +class VariantBasedMapAdaptorIterator + : public gsi::MapAdaptorIterator +{ +public: + VariantBasedMapAdaptorIterator (tl::Variant::array_iterator b, tl::Variant::array_iterator e, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k); + + virtual void get (SerialArgs &w, tl::Heap &heap) const; + virtual bool at_end () const; + virtual void inc (); + +private: + tl::Variant::array_iterator m_b, m_e; + const gsi::ArgType *mp_ainner, *mp_ainner_k; +}; + +/** + * @brief An adaptor for a vector which uses the tl::Variant's list perspective + */ +class VariantBasedMapAdaptor + : public gsi::MapAdaptor +{ +public: + VariantBasedMapAdaptor (tl::Variant *var, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k); + + virtual MapAdaptorIterator *create_iterator () const; + virtual void insert (SerialArgs &r, tl::Heap &heap); + virtual void clear (); + virtual size_t size () const; + virtual size_t serial_size () const; + +private: + const gsi::ArgType *mp_ainner, *mp_ainner_k; + tl::Variant *mp_var; +}; + +// --------------------------------------------------------------------- +// Writer function for serialization + +/** + * @brief Serialization of POD types + */ +template +struct writer +{ + void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *heap) + { + if (arg->is_nil () && atype.type () != gsi::T_var) { + + if (! (atype.is_ptr () || atype.is_cptr ())) { + throw tl::Exception (tl::to_string (tr ("Arguments of reference or direct type cannot be passed nil"))); + } else if (atype.is_ptr ()) { + aa->write ((R *)0); + } else { + aa->write ((const R *)0); + } + + } else { + + if (atype.is_ref () || atype.is_ptr ()) { + + // TODO: morph the variant to the requested type and pass its pointer (requires a non-const reference for arg) + // -> we would have a reference that can modify the argument (out parameter). + R *v = new R (var2c::get (*arg)); + heap->push (v); + + aa->write (v); + + } else if (atype.is_cref ()) { + // Note: POD's are written as copies for const refs, so we can pass a temporary here: + // (avoids having to create a temp object) + aa->write (var2c::get (*arg)); + } else if (atype.is_cptr ()) { + // Note: POD's are written as copies for const ptrs, so we can pass a temporary here: + // (avoids having to create a temp object) + R r = var2c::get (*arg); + aa->write (&r); + } else { + aa->write (var2c::get (*arg)); + } + + } + } +}; + +/** + * @brief Serialization for strings + */ +template <> +struct writer +{ + void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *) + { + // Cannot pass ownership currently + tl_assert (!atype.pass_obj ()); + + if (arg->is_nil ()) { + + if (! (atype.is_ptr () || atype.is_cptr ())) { + // nil is treated as an empty string for references + aa->write ((void *)new StringAdaptorImpl (std::string ())); + } else { + aa->write ((void *)0); + } + + } else { + + // TODO: morph the variant to the requested type and pass its pointer (requires a non-const reference for arg) + // -> we would have a reference that can modify the argument (out parameter). + // NOTE: by convention we pass the ownership to the receiver for adaptors. + aa->write ((void *)new StringAdaptorImpl (arg->to_string ())); + + } + } +}; + +/** + * @brief Specialization for Variant + */ +template <> +struct writer +{ + void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &, tl::Heap *) + { + // TODO: clarify: is nil a zero-pointer to a variant or a pointer to a "nil" variant? + // NOTE: by convention we pass the ownership to the receiver for adaptors. + aa->write ((void *)new VariantAdaptorImpl (arg)); + } +}; + +/** + * @brief Specialization for Vectors + */ +template <> +struct writer +{ + void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *) + { + if (arg->is_nil ()) { + if (! (atype.is_ptr () || atype.is_cptr ())) { + throw tl::Exception (tl::to_string (tr ("Arguments of reference or direct type cannot be passed nil"))); + } else { + aa->write ((void *)0); + } + } else { + tl_assert (atype.inner () != 0); + aa->write ((void *)new VariantBasedVectorAdaptor (arg, atype.inner ())); + } + } +}; + +/** + * @brief Specialization for Maps + */ +template <> +struct writer +{ + void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *) + { + if (arg->is_nil ()) { + if (! (atype.is_ptr () || atype.is_cptr ())) { + throw tl::Exception (tl::to_string (tr ("Arguments of reference or direct type cannot be passed nil"))); + } else { + aa->write ((void *)0); + } + } else { + tl_assert (atype.inner () != 0); + tl_assert (atype.inner_k () != 0); + aa->write ((void *)new VariantBasedMapAdaptor (arg, atype.inner (), atype.inner_k ())); + } + } +}; + +/** + * @brief Specialization for void + */ +template <> +struct writer +{ + void operator() (gsi::SerialArgs *, tl::Variant *, const gsi::ArgType &, tl::Heap *) + { + // nothing - void type won't be serialized + } +}; + +void push_args (gsi::SerialArgs &arglist, const tl::Variant &args, const gsi::MethodBase *meth, tl::Heap *heap); + +/** + * @brief Specialization for void + */ +template <> +struct writer +{ + void operator() (gsi::SerialArgs *aa, tl::Variant *arg, const gsi::ArgType &atype, tl::Heap *heap) + { + if (arg->is_nil ()) { + + if (atype.is_ref () || atype.is_cref ()) { + throw tl::Exception (tl::to_string (tr ("Cannot pass nil to reference parameters"))); + } else if (! atype.is_cptr () && ! atype.is_ptr ()) { + throw tl::Exception (tl::to_string (tr ("Cannot pass nil to direct parameters"))); + } + + aa->write ((void *) 0); + + } else if (arg->is_list ()) { + + // we may implicitly convert an array into a constructor call of a target object - + // for now we only check whether the number of arguments is compatible with the array given. + + int n = int (arg->size ()); + const gsi::MethodBase *meth = 0; + for (gsi::ClassBase::method_iterator c = atype.cls ()->begin_constructors (); c != atype.cls ()->end_constructors (); ++c) { + if ((*c)->compatible_with_num_args (n)) { + meth = *c; + break; + } + } + + if (!meth) { + throw tl::Exception (tl::to_string (tr ("No constructor of %s available that takes %d arguments (implicit call from tuple)")), atype.cls ()->name (), n); + } + + // implicit call of constructor + gsi::SerialArgs retlist (meth->retsize ()); + gsi::SerialArgs arglist (meth->argsize ()); + + push_args (arglist, *arg, meth, heap); + + meth->call (0, arglist, retlist); + + void *new_obj = retlist.read (*heap); + if (new_obj && (atype.is_ptr () || atype.is_cptr () || atype.is_ref () || atype.is_cref ())) { + // For pointers or refs, ownership over these objects is not transferred. + // Hence we have to keep them on the heap. + // TODO: what if the called method takes ownership using keep()? + heap->push (new gsi::ObjectHolder (atype.cls (), new_obj)); + } + + aa->write (new_obj); + + } else { + + if (! arg->is_user ()) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s)")), atype.cls ()->name ())); + } + + const tl::VariantUserClassBase *cls = arg->user_cls (); + if (!cls) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s)")), atype.cls ()->name ())); + } + if (cls->is_const () && (atype.is_ref () || atype.is_ptr ())) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot pass a const reference of class %s to a non-const reference or pointer parameter")), atype.cls ()->name ())); + } + + if (atype.is_ref () || atype.is_cref () || atype.is_ptr () || atype.is_cptr ()) { + + if (cls->gsi_cls ()->is_derived_from (atype.cls ())) { + + if (cls->gsi_cls ()->adapted_type_info ()) { + // resolved adapted type + aa->write ((void *) cls->gsi_cls ()->adapted_from_obj (get_object (*arg))); + } else { + aa->write (get_object (*arg)); + } + + } else if ((atype.is_cref () || atype.is_cptr ()) && cls->gsi_cls ()->can_convert_to (atype.cls ())) { + + // We can convert objects for cref and cptr, but ownership over these objects is not transferred. + // Hence we have to keep them on the heap. + void *new_obj = atype.cls ()->create_obj_from (cls->gsi_cls (), get_object (*arg)); + heap->push (new gsi::ObjectHolder (atype.cls (), new_obj)); + aa->write (new_obj); + + } else { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s)")), atype.cls ()->name ())); + } + + } else { + + if (cls->gsi_cls ()->is_derived_from (atype.cls ())) { + + if (cls->gsi_cls ()->adapted_type_info ()) { + aa->write (cls->gsi_cls ()->create_adapted_from_obj (get_object (*arg))); + } else { + aa->write ((void *) cls->gsi_cls ()->clone (get_object (*arg))); + } + + } else if (cls->gsi_cls ()->can_convert_to (atype.cls ())) { + + aa->write (atype.cls ()->create_obj_from (cls->gsi_cls (), get_object (*arg))); + + } else { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s)")), atype.cls ()->name ())); + } + + } + + } + + } +}; + +void push_arg (gsi::SerialArgs &arglist, const ArgType &atype, tl::Variant &arg, tl::Heap *heap) +{ + gsi::do_on_type () (atype.type (), &arglist, &arg, atype, heap); +} + +void push_args (gsi::SerialArgs &arglist, const tl::Variant &args, const gsi::MethodBase *meth, tl::Heap *heap) +{ + int n = int (args.size ()); + int narg = 0; + + for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && narg < n; ++a, ++narg) { + try { + // Note: this const_cast is ugly, but it will basically enable "out" parameters + // TODO: clean this up. + gsi::do_on_type () (a->type (), &arglist, const_cast ((args.get_list ().begin () + narg).operator-> ()), *a, heap); + } catch (tl::Exception &ex) { + std::string msg = ex.msg () + tl::sprintf (tl::to_string (tr (" (argument '%s')")), a->spec ()->name ()); + throw tl::Exception (msg); + } + } +} + +// --------------------------------------------------------------------- +// Reader function for serialization + +/** + * @brief A reader function + */ +template +struct reader +{ + void + operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) + { + if (atype.is_ref ()) { + *out = rr->template read (*heap); + } else if (atype.is_cref ()) { + *out = rr->template read (*heap); + } else if (atype.is_ptr ()) { + R *p = rr->template read (*heap); + if (p == 0) { + *out = tl::Variant (); + } else { + *out = *p; + } + } else if (atype.is_cptr ()) { + const R *p = rr->template read (*heap); + if (p == 0) { + *out = tl::Variant (); + } else { + *out = *p; + } + } else { + *out = rr->template read (*heap); + } + } +}; + +/** + * @brief A reader specialization for void * + */ +template <> +struct reader +{ + void + operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) + { + tl_assert (!atype.is_ref ()); + tl_assert (!atype.is_cref ()); + tl_assert (!atype.is_ptr ()); + tl_assert (!atype.is_cptr ()); + *out = size_t (rr->read (*heap)); + } +}; + +/** + * @brief A reader specialization for strings + */ +template <> +struct reader +{ + void + operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &, tl::Heap *heap) + { + std::unique_ptr a ((StringAdaptor *) rr->read(*heap)); + if (!a.get ()) { + *out = tl::Variant (); + } else { + *out = tl::Variant (std::string (a->c_str (), a->size ())); + } + } +}; + +/** + * @brief A reader specialization for variants + */ +template <> +struct reader +{ + void + operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &, tl::Heap *heap) + { + std::unique_ptr a ((VariantAdaptor *) rr->read(*heap)); + if (!a.get ()) { + *out = tl::Variant (); + } else { + *out = a->var (); + } + } +}; + +/** + * @brief A reader specialization for maps + */ +template <> +struct reader +{ + void + operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) + { + std::unique_ptr a ((MapAdaptor *) rr->read(*heap)); + if (!a.get ()) { + *out = tl::Variant (); + } else { + tl_assert (atype.inner () != 0); + tl_assert (atype.inner_k () != 0); + VariantBasedMapAdaptor t (out, atype.inner (), atype.inner_k ()); + a->copy_to (&t, *heap); + } + } +}; + +/** + * @brief A reader specialization for const char * + */ +template <> +struct reader +{ + void + operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) + { + std::unique_ptr a ((VectorAdaptor *) rr->read(*heap)); + if (!a.get ()) { + *out = tl::Variant (); + } else { + tl_assert (atype.inner () != 0); + VariantBasedVectorAdaptor t (out, atype.inner ()); + a->copy_to (&t, *heap); + } + } +}; + +/** + * @brief A reader specialization for objects + */ +template <> +struct reader +{ + void + operator() (tl::Variant *out, gsi::SerialArgs *rr, const gsi::ArgType &atype, tl::Heap *heap) + { + void *obj = rr->read (*heap); + + bool is_const = atype.is_cptr () || atype.is_cref (); + bool owner = true; + if (atype.is_ptr () || atype.is_cptr () || atype.is_ref () || atype.is_cref ()) { + owner = atype.pass_obj (); + } + bool can_destroy = atype.is_ptr () || owner; + + const gsi::ClassBase *clsact = atype.cls ()->subclass_decl (obj); + tl_assert (clsact != 0); + + if (obj == 0) { + + *out = tl::Variant (); + + } else if (!clsact->adapted_type_info () && clsact->is_managed ()) { + + // gsi::ObjectBase-based objects can be managed by reference since they + // provide a tl::Object through the proxy. + + *out = tl::Variant (); + + const tl::VariantUserClassBase *cls = clsact->var_cls (atype.is_cref () || atype.is_cptr ()); + tl_assert (cls != 0); + + Proxy *proxy = clsact->gsi_object (obj)->find_client (); + if (proxy) { + + out->set_user_ref (proxy, cls, false); + + } else { + + // establish a new proxy + proxy = new Proxy (clsact); + proxy->set (obj, owner, is_const, can_destroy); + + out->set_user_ref (proxy, cls, owner); + + } + + } else { + + const tl::VariantUserClassBase *cls = 0; + + if (clsact->adapted_type_info ()) { + // create an adaptor from an adapted type + if (owner) { + obj = clsact->create_from_adapted_consume (obj); + } else { + obj = clsact->create_from_adapted (obj); + } + cls = clsact->var_cls (false); + } else { + cls = clsact->var_cls (is_const); + } + + tl_assert (cls != 0); + *out = tl::Variant (); + + // consider prefer_copy + if (! owner && atype.prefer_copy () && !clsact->is_managed () && clsact->can_copy ()) { + obj = clsact->clone (obj); + owner = true; + } + + out->set_user (obj, cls, owner); + + } + } +}; + +/** + * @brief A reader specialization for new objects + */ +template <> +struct reader +{ + void + operator() (tl::Variant *, gsi::SerialArgs *, const gsi::ArgType &, tl::Heap *) + { + // nothing - void type won't be serialized + } +}; + +// --------------------------------------------------------------------- +// VariantBasedVectorAdaptorIterator implementation + +VariantBasedVectorAdaptorIterator::VariantBasedVectorAdaptorIterator (tl::Variant::iterator b, tl::Variant::iterator e, const gsi::ArgType *ainner) + : m_b (b), m_e (e), mp_ainner (ainner) +{ + // .. nothing yet .. +} + +void VariantBasedVectorAdaptorIterator::get (SerialArgs &w, tl::Heap &heap) const +{ + gsi::do_on_type () (mp_ainner->type (), &w, &*m_b, *mp_ainner, &heap); +} + +bool VariantBasedVectorAdaptorIterator::at_end () const +{ + return m_b == m_e; +} + +void VariantBasedVectorAdaptorIterator::inc () +{ + ++m_b; +} + +// --------------------------------------------------------------------- +// VariantBasedVectorAdaptor implementation + +VariantBasedVectorAdaptor::VariantBasedVectorAdaptor (tl::Variant *var, const gsi::ArgType *ainner) + : mp_ainner (ainner), mp_var (var) +{ +} + +VectorAdaptorIterator *VariantBasedVectorAdaptor::create_iterator () const +{ + return new VariantBasedVectorAdaptorIterator (mp_var->begin (), mp_var->end (), mp_ainner); +} + +void VariantBasedVectorAdaptor::push (SerialArgs &r, tl::Heap &heap) +{ + tl::Variant member; + gsi::do_on_type () (mp_ainner->type (), &member, &r, *mp_ainner, &heap); + mp_var->push (member); +} + +void VariantBasedVectorAdaptor::clear () +{ + mp_var->set_list (); +} + +size_t VariantBasedVectorAdaptor::size () const +{ + return mp_var->size (); +} + +size_t VariantBasedVectorAdaptor::serial_size () const +{ + return mp_ainner->size (); +} + +// --------------------------------------------------------------------- +// VariantBasedMapAdaptorIterator implementation + +VariantBasedMapAdaptorIterator::VariantBasedMapAdaptorIterator (tl::Variant::array_iterator b, tl::Variant::array_iterator e, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k) + : m_b (b), m_e (e), mp_ainner (ainner), mp_ainner_k (ainner_k) +{ + // .. nothing yet .. +} + +void VariantBasedMapAdaptorIterator::get (SerialArgs &w, tl::Heap &heap) const +{ + // Note: the const_cast is ugly but in this context we won't modify the variant given as the key. + // And it lets us keep the interface tidy. + gsi::do_on_type () (mp_ainner_k->type (), &w, const_cast (&m_b->first), *mp_ainner_k, &heap); + gsi::do_on_type () (mp_ainner->type (), &w, &m_b->second, *mp_ainner, &heap); +} + +bool VariantBasedMapAdaptorIterator::at_end () const +{ + return m_b == m_e; +} + +void VariantBasedMapAdaptorIterator::inc () +{ + ++m_b; +} + +// --------------------------------------------------------------------- +// VariantBasedMapAdaptor implementation + +VariantBasedMapAdaptor::VariantBasedMapAdaptor (tl::Variant *var, const gsi::ArgType *ainner, const gsi::ArgType *ainner_k) + : mp_ainner (ainner), mp_ainner_k (ainner_k), mp_var (var) +{ +} + +MapAdaptorIterator *VariantBasedMapAdaptor::create_iterator () const +{ + return new VariantBasedMapAdaptorIterator (mp_var->begin_array (), mp_var->end_array (), mp_ainner, mp_ainner_k); +} + +void VariantBasedMapAdaptor::insert (SerialArgs &r, tl::Heap &heap) +{ + tl::Variant k, v; + gsi::do_on_type () (mp_ainner_k->type (), &k, &r, *mp_ainner_k, &heap); + gsi::do_on_type () (mp_ainner->type (), &v, &r, *mp_ainner, &heap); + mp_var->insert (k, v); +} + +void VariantBasedMapAdaptor::clear () +{ + mp_var->set_array (); +} + +size_t VariantBasedMapAdaptor::size () const +{ + return mp_var->array_size (); +} + +size_t VariantBasedMapAdaptor::serial_size () const +{ + return mp_ainner_k->size () + mp_ainner->size (); +} + +// --------------------------------------------------------------------- +// pop_arg implementation + +void +pull_arg (gsi::SerialArgs &retlist, const gsi::ArgType &atype, tl::Variant &arg_out, tl::Heap *heap) +{ + gsi::do_on_type () (atype.type (), &arg_out, &retlist, atype, heap); +} + +} diff --git a/src/gsi/gsi/gsiVariantArgs.h b/src/gsi/gsi/gsiVariantArgs.h new file mode 100644 index 000000000..6bd2361a3 --- /dev/null +++ b/src/gsi/gsi/gsiVariantArgs.h @@ -0,0 +1,81 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2023 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_gsiVariantArgs +#define HDR_gsiVariantArgs + +#include "gsiCommon.h" +#include "gsiSerialisation.h" +#include "gsiTypes.h" + +namespace tl +{ + class Heap; + class Variant; +} + +namespace gsi +{ + +class SerialArgs; +class ArgType; + +/** + * @brief Pushes a variant on the serialization stack + * + * This also involves expanding of arrays into objects by calling the constructor. + * + * @param arglist The serialization stack to push the argument to + * @param atype The argument type + * @param arg The argument to push (may be modified if 'out' parameter) + * @param heap The heap + */ +GSI_PUBLIC void push_arg (gsi::SerialArgs &arglist, const gsi::ArgType &atype, tl::Variant &arg, tl::Heap *heap); + +/** + * @brief Pulls a variant from the serialization stack + * + * This function will pull the next argument from the bottom of the serialization stack + * and remove it from the stack. + * + * @param retlist The serialization stack + * @param atype The argument type + * @param arg_out Receives the value + * @param heap The heap + */ +GSI_PUBLIC void pull_arg (gsi::SerialArgs &retlist, const gsi::ArgType &atype, tl::Variant &arg_out, tl::Heap *heap); + +/** + * @brief Tests if the argument can be passed to a specific type + * @param atype The argument type + * @param arg The value to pass to it + * @param loose true for loose checking + * + * @return True, if the argument can be passed + */ +GSI_PUBLIC bool test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose); + +} + +#endif + diff --git a/src/pya/pya/pyaCallables.cc b/src/pya/pya/pyaCallables.cc index f63f21976..d35f8bff9 100644 --- a/src/pya/pya/pyaCallables.cc +++ b/src/pya/pya/pyaCallables.cc @@ -27,7 +27,9 @@ #include "pyaMarshal.h" #include "pyaConvert.h" #include "pyaUtils.h" + #include "gsiMethods.h" +#include "gsiVariantArgs.h" namespace pya { @@ -178,15 +180,97 @@ get_return_value (PYAObjectBase *self, gsi::SerialArgs &retlist, const gsi::Meth } else { - ret = pop_arg (meth->ret_type (), retlist, self, heap).release (); + ret = pull_arg (meth->ret_type (), retlist, self, heap).release (); } return ret; } +inline int +num_args (const gsi::MethodBase *m) +{ + return int (m->end_arguments () - m->begin_arguments ()); +} + +static bool +compatible_with_args (const gsi::MethodBase *m, int argc, PyObject *kwargs) +{ + int nargs = num_args (m); + + if (argc >= nargs) { + // no more arguments to consider + return argc == nargs && (kwargs == NULL || PyDict_Size (kwargs) == 0); + } + + if (kwargs != NULL) { + + int nkwargs = int (PyDict_Size (kwargs)); + int kwargs_taken = 0; + + while (argc < nargs) { + const gsi::ArgType &atype = m->begin_arguments () [argc]; + pya::PythonPtr py_arg = PyDict_GetItemString (kwargs, atype.spec ()->name ().c_str ()); + if (! py_arg) { + if (! atype.spec ()->has_default ()) { + return false; + } + } else { + ++kwargs_taken; + } + ++argc; + } + + // matches if all keyword arguments are taken + return kwargs_taken == nkwargs; + + } else { + + while (argc < nargs) { + const gsi::ArgType &atype = m->begin_arguments () [argc]; + if (! atype.spec ()->has_default ()) { + return false; + } + ++argc; + } + + return true; + + } +} + +static std::string +describe_overload (const gsi::MethodBase *m, int argc, PyObject *kwargs) +{ + std::string res = m->to_string (); + if (compatible_with_args (m, argc, kwargs)) { + res += " " + tl::to_string (tr ("[match candidate]")); + } + return res; +} + +static std::string +describe_overloads (const MethodTable *mt, int mid, int argc, PyObject *kwargs) +{ + std::string res; + for (auto m = mt->begin (mid); m != mt->end (mid); ++m) { + res += std::string (" ") + describe_overload (*m, argc, kwargs) + "\n"; + } + return res; +} + +static PyObject * +get_kwarg (const gsi::ArgType &atype, PyObject *kwargs) +{ + if (kwargs != NULL) { + return PyDict_GetItemString (kwargs, atype.spec ()->name ().c_str ()); + } else { + return NULL; + } +} + static const gsi::MethodBase * -match_method (int mid, PyObject *self, PyObject *args, bool strict) +match_method (int mid, PyObject *self, PyObject *args, PyObject *kwargs, bool strict) { const gsi::ClassBase *cls_decl = 0; @@ -226,7 +310,7 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) // ignore callbacks - } else if ((*m)->compatible_with_num_args (argc)) { + } else if (compatible_with_args (*m, argc, kwargs)) { ++candidates; meth = *m; @@ -237,28 +321,11 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) // no candidate -> error if (! meth) { - if (! strict) { return 0; + } else { + throw tl::TypeError (tl::to_string (tr ("Can't match arguments. Variants are:\n")) + describe_overloads (mt, mid, argc, kwargs)); } - - std::set nargs; - for (MethodTableEntry::method_iterator m = mt->begin (mid); m != mt->end (mid); ++m) { - if (! (*m)->is_callback ()) { - nargs.insert (std::distance ((*m)->begin_arguments (), (*m)->end_arguments ())); - } - } - - std::string nargs_s; - for (std::set::const_iterator na = nargs.begin (); na != nargs.end (); ++na) { - if (na != nargs.begin ()) { - nargs_s += "/"; - } - nargs_s += tl::to_string (*na); - } - - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid number of arguments (got %d, expected %s)")), argc, nargs_s)); - } // more than one candidate -> refine by checking the arguments @@ -274,14 +341,18 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) if (! (*m)->is_callback ()) { // check arguments (count and type) - bool is_valid = (*m)->compatible_with_num_args (argc); + + bool is_valid = compatible_with_args (*m, argc, kwargs); + int sc = 0; int i = 0; - for (gsi::MethodBase::argument_iterator a = (*m)->begin_arguments (); is_valid && i < argc && a != (*m)->end_arguments (); ++a, ++i) { - PyObject *arg = is_tuple ? PyTuple_GetItem (args, i) : PyList_GetItem (args, i); - if (test_arg (*a, arg, false /*strict*/)) { + for (gsi::MethodBase::argument_iterator a = (*m)->begin_arguments (); is_valid && a != (*m)->end_arguments (); ++a, ++i) { + PythonPtr arg (i >= argc ? get_kwarg (*a, kwargs) : (is_tuple ? PyTuple_GetItem (args, i) : PyList_GetItem (args, i))); + if (! arg) { + is_valid = a->spec ()->has_default (); + } else if (test_arg (*a, arg.get (), false /*strict*/)) { ++sc; - } else if (test_arg (*a, arg, true /*loose*/)) { + } else if (test_arg (*a, arg.get (), true /*loose*/)) { // non-scoring match } else { is_valid = false; @@ -306,12 +377,17 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) if (is_valid) { - // otherwise take the candidate with the better score - if (candidates > 0 && sc > score) { - candidates = 1; - meth = *m; - score = sc; - } else if (candidates == 0 || sc == score) { + // otherwise take the candidate with the better score or the least number of arguments (faster) + if (candidates > 0) { + if (sc > score || (sc == score && num_args (meth) > num_args (*m))) { + candidates = 1; + meth = *m; + score = sc; + } else if (sc == score && num_args (meth) == num_args (*m)) { + ++candidates; + meth = *m; + } + } else { ++candidates; meth = *m; score = sc; @@ -327,9 +403,9 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) // one candidate, but needs checking whether compatibility is given - this avoid having to route NotImplemented over TypeError exceptions later int i = 0; - for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); i < argc && a != meth->end_arguments (); ++a, ++i) { - PyObject *arg = is_tuple ? PyTuple_GetItem (args, i) : PyList_GetItem (args, i); - if (! test_arg (*a, arg, true /*loose*/)) { + for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a, ++i) { + PythonPtr arg (i >= argc ? get_kwarg (*a, kwargs) : (is_tuple ? PyTuple_GetItem (args, i) : PyList_GetItem (args, i))); + if (arg && ! test_arg (*a, arg.get (), true /*loose*/)) { return 0; } } @@ -340,7 +416,7 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) if (! strict || mt->fallback_not_implemented (mid)) { return 0; } else { - throw tl::TypeError (tl::to_string (tr ("No overload with matching arguments"))); + throw tl::TypeError (tl::to_string (tr ("No overload with matching arguments. Variants are:\n")) + describe_overloads (mt, mid, argc, kwargs)); } } @@ -348,7 +424,7 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict) if (! strict || mt->fallback_not_implemented (mid)) { return 0; } else { - throw tl::TypeError (tl::to_string (tr ("Ambiguous overload variants - multiple method declarations match arguments"))); + throw tl::TypeError (tl::to_string (tr ("Ambiguous overload variants - multiple method declarations match arguments. Variants are:\n")) + describe_overloads (mt, mid, argc, kwargs)); } } @@ -611,16 +687,64 @@ special_method_impl (gsi::MethodBase::special_method_type smt, PyObject *self, P } void -push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args, tl::Heap &heap) +push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args, PyObject *kwargs, tl::Heap &heap) { bool is_tuple = PyTuple_Check (args); - int i = 0; + int iarg = 0; int argc = args == NULL ? 0 : (is_tuple ? int (PyTuple_Size (args)) : int (PyList_Size (args))); + int kwargs_taken = 0; + int nkwargs = kwargs == NULL ? 0 : int (PyDict_Size (kwargs)); try { - for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); i < argc && a != meth->end_arguments (); ++a, ++i) { - push_arg (*a, arglist, is_tuple ? PyTuple_GetItem (args, i) : PyList_GetItem (args, i), heap); + for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a, ++iarg) { + PythonPtr arg (iarg >= argc ? get_kwarg (*a, kwargs) : (is_tuple ? PyTuple_GetItem (args, iarg) : PyList_GetItem (args, iarg))); + if (! arg) { + if (a->spec ()->has_default ()) { + if (kwargs_taken == nkwargs) { + // leave it to the consumer to establish the default values (that is faster) + break; + } + tl::Variant def_value = a->spec ()->default_value (); + gsi::push_arg (arglist, *a, def_value, &heap); + } else { + throw tl::Exception (tl::to_string ("No argument provided (positional or keyword) and no default value available")); + } + } else { + if (iarg >= argc) { + ++kwargs_taken; + } + push_arg (*a, arglist, arg.get (), heap); + } + } + + if (kwargs_taken != nkwargs) { + + // check if there are any left-over keyword parameters with unknown names + + pya::PythonRef keys (PyDict_Keys (kwargs)); + + std::set valid_names; + for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a) { + valid_names.insert (a->spec ()->name ()); + } + + std::set invalid_names; + for (int i = int (PyList_Size (keys.get ())); i > 0; ) { + --i; + std::string k = python2c (PyList_GetItem (keys.get (), i)); + if (valid_names.find (k) == valid_names.end ()) { + invalid_names.insert (k); + } + } + + if (invalid_names.size () > 1) { + std::string names_str = tl::join (invalid_names.begin (), invalid_names.end (), ", "); + throw tl::Exception (tl::to_string (tr ("Unknown keyword parameters: ")) + names_str); + } else if (invalid_names.size () == 1) { + throw tl::Exception (tl::to_string (tr ("Unknown keyword parameter: ")) + *invalid_names.begin ()); + } + } } catch (tl::Exception &ex) { @@ -628,19 +752,26 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args // In case of an error upon write, pop the arguments to clean them up. // Without this, there is a risk to keep dead objects on the stack. for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && arglist; ++a) { - pop_arg (*a, arglist, 0, heap); + pull_arg (*a, arglist, 0, heap); } - std::string msg; - const gsi::ArgSpecBase *arg_spec = meth->begin_arguments () [i].spec (); + if (iarg < num_args (meth)) { + + // attach argument information to the error message if available + + std::string msg; + const gsi::ArgSpecBase *arg_spec = meth->begin_arguments () [iarg].spec (); + + if (arg_spec && ! arg_spec->name ().empty ()) { + msg = tl::sprintf (tl::to_string (tr ("%s for argument #%d ('%s')")), ex.basic_msg (), iarg + 1, arg_spec->name ()); + } else { + msg = tl::sprintf (tl::to_string (tr ("%s for argument #%d")), ex.basic_msg (), iarg + 1); + } + + ex.set_basic_msg (msg); - if (arg_spec && ! arg_spec->name ().empty ()) { - msg = tl::sprintf (tl::to_string (tr ("%s for argument #%d ('%s')")), ex.basic_msg (), i + 1, arg_spec->name ()); - } else { - msg = tl::sprintf (tl::to_string (tr ("%s for argument #%d")), ex.basic_msg (), i + 1); } - ex.set_basic_msg (msg); throw; } catch (...) { @@ -648,7 +779,7 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args // In case of an error upon write, pop the arguments to clean them up. // Without this, there is a risk to keep dead objects on the stack. for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && arglist; ++a) { - pop_arg (*a, arglist, 0, heap); + pull_arg (*a, arglist, 0, heap); } throw; @@ -657,13 +788,13 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args } static PyObject * -method_adaptor (int mid, PyObject *self, PyObject *args) +method_adaptor (int mid, PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *ret = NULL; PYA_TRY - const gsi::MethodBase *meth = match_method (mid, self, args, true); + const gsi::MethodBase *meth = match_method (mid, self, args, kwargs, true); // method is not implemented if (! meth) { @@ -703,7 +834,7 @@ method_adaptor (int mid, PyObject *self, PyObject *args) gsi::SerialArgs retlist (meth->retsize ()); gsi::SerialArgs arglist (meth->argsize ()); - push_args (arglist, meth, args, heap); + push_args (arglist, meth, args, kwargs, heap); meth->call (obj, arglist, retlist); @@ -770,7 +901,7 @@ property_setter_adaptor (int mid, PyObject *self, PyObject *args) * @brief __init__ implementation (bound to method ith id 'mid') */ static PyObject * -method_init_adaptor (int mid, PyObject *self, PyObject *args) +method_init_adaptor (int mid, PyObject *self, PyObject *args, PyObject *kwargs) { PYA_TRY @@ -782,7 +913,10 @@ method_init_adaptor (int mid, PyObject *self, PyObject *args) } int argc = PyTuple_Check (args) ? int (PyTuple_Size (args)) : int (PyList_Size (args)); - const gsi::MethodBase *meth = match_method (mid, self, args, argc > 0 || ! p->cls_decl ()->can_default_create ()); + bool has_kwargs = kwargs != NULL && PyDict_Size (kwargs) > 0; + bool strict_matching = argc > 0 || has_kwargs || ! p->cls_decl ()->can_default_create (); + + const gsi::MethodBase *meth = match_method (mid, self, args, kwargs, strict_matching); if (meth && meth->smt () == gsi::MethodBase::None) { @@ -791,7 +925,7 @@ method_init_adaptor (int mid, PyObject *self, PyObject *args) gsi::SerialArgs retlist (meth->retsize ()); gsi::SerialArgs arglist (meth->argsize ()); - push_args (arglist, meth, args, heap); + push_args (arglist, meth, args, kwargs, heap); meth->call (0, arglist, retlist); @@ -979,8 +1113,8 @@ property_setter_impl (int mid, PyObject *self, PyObject *value) } if (is_valid) { - ++candidates; meth = *m; + ++candidates; } } @@ -1075,12 +1209,12 @@ property_setter_func (PyObject *self, PyObject *value, void *closure) // Adaptor arrays template -PyObject *method_adaptor (PyObject *self, PyObject *args) +PyObject *method_adaptor (PyObject *self, PyObject *args, PyObject *kwargs) { - return method_adaptor (N, self, args); + return method_adaptor (N, self, args, kwargs); } -static py_func_ptr_t method_adaptors [] = +static py_func_with_kw_ptr_t method_adaptors [] = { &method_adaptor<0x000>, &method_adaptor<0x001>, &method_adaptor<0x002>, &method_adaptor<0x003>, &method_adaptor<0x004>, &method_adaptor<0x005>, &method_adaptor<0x006>, &method_adaptor<0x007>, &method_adaptor<0x008>, &method_adaptor<0x009>, &method_adaptor<0x00a>, &method_adaptor<0x00b>, &method_adaptor<0x00c>, &method_adaptor<0x00d>, &method_adaptor<0x00e>, &method_adaptor<0x00f>, @@ -1244,7 +1378,7 @@ static py_func_ptr_t method_adaptors [] = &method_adaptor<0x4f8>, &method_adaptor<0x4f9>, &method_adaptor<0x4fa>, &method_adaptor<0x4fb>, &method_adaptor<0x4fc>, &method_adaptor<0x4fd>, &method_adaptor<0x4fe>, &method_adaptor<0x4ff>, }; -py_func_ptr_t get_method_adaptor (int n) +py_func_with_kw_ptr_t get_method_adaptor (int n) { tl_assert (n >= 0 && n < int (sizeof (method_adaptors) / sizeof (method_adaptors [0]))); return method_adaptors [n]; @@ -1603,12 +1737,12 @@ py_func_ptr_t get_property_setter_adaptor (int n) } template -PyObject *method_init_adaptor (PyObject *self, PyObject *args) +PyObject *method_init_adaptor (PyObject *self, PyObject *args, PyObject *kwargs) { - return method_init_adaptor (N, self, args); + return method_init_adaptor (N, self, args, kwargs); } -static py_func_ptr_t method_init_adaptors [] = +static py_func_with_kw_ptr_t method_init_adaptors [] = { &method_init_adaptor<0x000>, &method_init_adaptor<0x001>, &method_init_adaptor<0x002>, &method_init_adaptor<0x003>, &method_init_adaptor<0x004>, &method_init_adaptor<0x005>, &method_init_adaptor<0x006>, &method_init_adaptor<0x007>, &method_init_adaptor<0x008>, &method_init_adaptor<0x009>, &method_init_adaptor<0x00a>, &method_init_adaptor<0x00b>, &method_init_adaptor<0x00c>, &method_init_adaptor<0x00d>, &method_init_adaptor<0x00e>, &method_init_adaptor<0x00f>, @@ -1740,7 +1874,7 @@ static py_func_ptr_t method_init_adaptors [] = &method_init_adaptor<0x3f8>, &method_init_adaptor<0x3f9>, &method_init_adaptor<0x3fa>, &method_init_adaptor<0x3fb>, &method_init_adaptor<0x3fc>, &method_init_adaptor<0x3fd>, &method_init_adaptor<0x3fe>, &method_init_adaptor<0x3ff>, }; -py_func_ptr_t get_method_init_adaptor (int n) +py_func_with_kw_ptr_t get_method_init_adaptor (int n) { tl_assert (n >= 0 && n < int (sizeof (method_init_adaptors) / sizeof (method_init_adaptors [0]))); return method_init_adaptors [n]; diff --git a/src/pya/pya/pyaCallables.h b/src/pya/pya/pyaCallables.h index 2610c6032..1d19f70e5 100644 --- a/src/pya/pya/pyaCallables.h +++ b/src/pya/pya/pyaCallables.h @@ -39,12 +39,13 @@ PyObject *object_default_le_impl (PyObject *self, PyObject *args); PyObject *object_default_gt_impl (PyObject *self, PyObject *args); PyObject *object_default_deepcopy_impl (PyObject *self, PyObject *args); +typedef PyObject *(*py_func_with_kw_ptr_t) (PyObject *, PyObject *, PyObject *); typedef PyObject *(*py_func_ptr_t) (PyObject *, PyObject *); -py_func_ptr_t get_method_adaptor (int n); +py_func_with_kw_ptr_t get_method_adaptor (int n); py_func_ptr_t get_property_getter_adaptor (int n); py_func_ptr_t get_property_setter_adaptor (int n); -py_func_ptr_t get_method_init_adaptor (int n); +py_func_with_kw_ptr_t get_method_init_adaptor (int n); inline void *make_closure (int mid_getter, int mid_setter) { diff --git a/src/pya/pya/pyaHelpers.cc b/src/pya/pya/pyaHelpers.cc index c8aa44c60..e85727d2c 100644 --- a/src/pya/pya/pyaHelpers.cc +++ b/src/pya/pya/pyaHelpers.cc @@ -379,7 +379,7 @@ pya_plain_iterator_next (PyObject *self) gsi::SerialArgs args (iter->iter->serial_size ()); iter->iter->get (args); - PythonRef obj = pop_arg (*iter->value_type, args, 0, heap); + PythonRef obj = pull_arg (*iter->value_type, args, 0, heap); return obj.release (); } diff --git a/src/pya/pya/pyaMarshal.cc b/src/pya/pya/pyaMarshal.cc index 38c659e4d..ad658fc83 100644 --- a/src/pya/pya/pyaMarshal.cc +++ b/src/pya/pya/pyaMarshal.cc @@ -32,7 +32,7 @@ namespace pya { -void push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args, tl::Heap &heap); +void push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args, PyObject *kwargs, tl::Heap &heap); // ------------------------------------------------------------------- // Serialization adaptors for strings, variants, vectors and maps @@ -508,7 +508,7 @@ struct writer gsi::SerialArgs retlist (meth->retsize ()); gsi::SerialArgs arglist (meth->argsize ()); - push_args (arglist, meth, arg, *heap); + push_args (arglist, meth, arg, NULL, *heap); meth->call (0, arglist, retlist); @@ -862,7 +862,7 @@ struct reader }; PythonRef -pop_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PYAObjectBase *self, tl::Heap &heap) +pull_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PYAObjectBase *self, tl::Heap &heap) { PythonRef ret; gsi::do_on_type () (atype.type (), &aserial, &ret, self, atype, &heap); diff --git a/src/pya/pya/pyaMarshal.h b/src/pya/pya/pyaMarshal.h index 9306dbd95..a698d14d5 100644 --- a/src/pya/pya/pyaMarshal.h +++ b/src/pya/pya/pyaMarshal.h @@ -58,7 +58,7 @@ push_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PyObject *arg, tl * @param self The self object of the method call (for shortcut evaluation to return self if possible) * @return The deserialized object (a new reference) */ -PythonRef pop_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PYAObjectBase *self, tl::Heap &heap); +PythonRef pull_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PYAObjectBase *self, tl::Heap &heap); /** * @brief Tests whether the given object is compatible with the given type diff --git a/src/pya/pya/pyaModule.cc b/src/pya/pya/pyaModule.cc index f6e5b32f6..ce95f1593 100644 --- a/src/pya/pya/pyaModule.cc +++ b/src/pya/pya/pyaModule.cc @@ -517,12 +517,12 @@ public: // Special handling needed as the memo argument needs to be ignored method->ml_meth = &object_default_deepcopy_impl; } else if (mt->is_init (mid)) { - method->ml_meth = (PyCFunction) get_method_init_adaptor (mid); + method->ml_meth = reinterpret_cast (get_method_init_adaptor (mid)); } else { - method->ml_meth = (PyCFunction) get_method_adaptor (mid); + method->ml_meth = reinterpret_cast (get_method_adaptor (mid)); } method->ml_doc = mp_module->make_string (doc); - method->ml_flags = METH_VARARGS; + method->ml_flags = METH_VARARGS | METH_KEYWORDS; PythonRef attr = PythonRef (PyDescr_NewMethod (type, method)); set_type_attr (type, name, attr); @@ -531,9 +531,9 @@ public: PyMethodDef *method = mp_module->make_method_def (); method->ml_name = mp_module->make_string (name); - method->ml_meth = (PyCFunction) get_method_adaptor (mid); + method->ml_meth = reinterpret_cast (get_method_adaptor (mid)); method->ml_doc = mp_module->make_string (doc); - method->ml_flags = METH_VARARGS | METH_CLASS; + method->ml_flags = METH_VARARGS | METH_KEYWORDS | METH_CLASS; PythonRef attr = PythonRef (PyDescr_NewClassMethod (type, method)); set_type_attr (type, name, attr); diff --git a/src/pya/pya/pyaObject.cc b/src/pya/pya/pyaObject.cc index 86f9272cc..4899e0c39 100644 --- a/src/pya/pya/pyaObject.cc +++ b/src/pya/pya/pyaObject.cc @@ -145,7 +145,7 @@ Callee::call (int id, gsi::SerialArgs &args, gsi::SerialArgs &ret) const // TODO: callbacks with default arguments? for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) { - PyTuple_SetItem (argv.get (), arg4self + (a - meth->begin_arguments ()), pop_arg (*a, args, 0, heap).release ()); + PyTuple_SetItem (argv.get (), arg4self + (a - meth->begin_arguments ()), pull_arg (*a, args, 0, heap).release ()); } PythonRef result (PyObject_CallObject (callable.get (), argv.get ())); diff --git a/src/pya/pya/pyaSignalHandler.cc b/src/pya/pya/pyaSignalHandler.cc index 53ff51f8f..bc635c294 100644 --- a/src/pya/pya/pyaSignalHandler.cc +++ b/src/pya/pya/pyaSignalHandler.cc @@ -130,7 +130,7 @@ void SignalHandler::call (const gsi::MethodBase *meth, gsi::SerialArgs &args, gs int args_avail = int (std::distance (meth->begin_arguments (), meth->end_arguments ())); PythonRef argv (PyTuple_New (args_avail)); for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) { - PyTuple_SetItem (argv.get (), int (a - meth->begin_arguments ()), pop_arg (*a, args, 0, heap).release ()); + PyTuple_SetItem (argv.get (), int (a - meth->begin_arguments ()), pull_arg (*a, args, 0, heap).release ()); } // NOTE: in case one event handler deletes the object, it's safer to first collect the handlers and diff --git a/src/pya/unit_tests/pyaTests.cc b/src/pya/unit_tests/pyaTests.cc index 1da344769..69edf6cd3 100644 --- a/src/pya/unit_tests/pyaTests.cc +++ b/src/pya/unit_tests/pyaTests.cc @@ -96,6 +96,7 @@ void run_pythontest (tl::TestBase *_this, const std::string &fn) #define PYTHONTEST(n, file) \ TEST(n) { run_pythontest(_this, file); } +PYTHONTEST (kwargs, "kwargs.py") PYTHONTEST (dbLayoutTest, "dbLayoutTest.py") PYTHONTEST (dbRegionTest, "dbRegionTest.py") PYTHONTEST (dbReaders, "dbReaders.py") diff --git a/testdata/python/kwargs.py b/testdata/python/kwargs.py new file mode 100644 index 000000000..8f30b95d9 --- /dev/null +++ b/testdata/python/kwargs.py @@ -0,0 +1,209 @@ +# KLayout Layout Viewer +# Copyright (C) 2006-2023 Matthias Koefferlein +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import pya +import unittest + +# NOTE: pya.CplxTrans and pya.Trans and good test cases +# for the keyword arguments feature + +class KWArgsTest(unittest.TestCase): + + def test_1(self): + + t = pya.CplxTrans() + self.assertEqual(str(t), "r0 *1 0,0") + + t = pya.CplxTrans(1.5) + self.assertEqual(str(t), "r0 *1.5 0,0") + + t = pya.CplxTrans(1, 2) + self.assertEqual(str(t), "r0 *1 1,2") + + t = pya.CplxTrans(1, y = 2) + self.assertEqual(str(t), "r0 *1 1,2") + + t = pya.CplxTrans(x = 1, y = 2) + self.assertEqual(str(t), "r0 *1 1,2") + + t = pya.CplxTrans(u = pya.DVector(1, 2)) + self.assertEqual(str(t), "r0 *1 1,2") + + t = pya.CplxTrans(pya.DVector(1, 2)) + self.assertEqual(str(t), "r0 *1 1,2") + + t = pya.CplxTrans(u = pya.Vector(1, 2)) + self.assertEqual(str(t), "r0 *1 1,2") + + t = pya.CplxTrans(u = (1, 2)) + self.assertEqual(str(t), "r0 *1 1,2") + + t = pya.CplxTrans(mag = 1.5) + self.assertEqual(str(t), "r0 *1.5 0,0") + + t = pya.CplxTrans(1.5, 45, True, 1, 2) + self.assertEqual(str(t), "m22.5 *1.5 1,2") + + t = pya.CplxTrans(1.5, 45, True, pya.DVector(1, 2)) + self.assertEqual(str(t), "m22.5 *1.5 1,2") + + t = pya.CplxTrans(1.5, x = 1, y = 2, mirrx = True, rot = 45) + self.assertEqual(str(t), "m22.5 *1.5 1,2") + + t = pya.CplxTrans(pya.CplxTrans.M0) + self.assertEqual(str(t), "m0 *1 0,0") + + t = pya.CplxTrans(pya.CplxTrans.M0, u = pya.DVector(1, 2)) + self.assertEqual(str(t), "m0 *1 1,2") + + t = pya.CplxTrans(pya.CplxTrans.M0, mag = 1.5, u = pya.DVector(1, 2)) + self.assertEqual(str(t), "m0 *1.5 1,2") + + t = pya.CplxTrans(pya.CplxTrans.M0, 1.5, pya.DVector(1, 2)) + self.assertEqual(str(t), "m0 *1.5 1,2") + + t = pya.CplxTrans(pya.CplxTrans.M0, mag = 1.5, x = 1, y = 2) + self.assertEqual(str(t), "m0 *1.5 1,2") + + t = pya.CplxTrans(pya.CplxTrans.M0, 1.5, 1, 2) + self.assertEqual(str(t), "m0 *1.5 1,2") + + t = pya.CplxTrans(pya.VCplxTrans.M0) + self.assertEqual(str(t), "m0 *1 0,0") + + t = pya.CplxTrans(pya.ICplxTrans.M0) + self.assertEqual(str(t), "m0 *1 0,0") + + t = pya.CplxTrans(pya.DCplxTrans.M0) + self.assertEqual(str(t), "m0 *1 0,0") + + t = pya.CplxTrans(pya.Trans.M0) + self.assertEqual(str(t), "m0 *1 0,0") + + t = pya.CplxTrans(pya.Trans.M0, 1.5) + self.assertEqual(str(t), "m0 *1.5 0,0") + + t = pya.CplxTrans(pya.Trans.M0, mag = 1.5) + self.assertEqual(str(t), "m0 *1.5 0,0") + + t = pya.CplxTrans(t = pya.Trans.M0, mag = 1.5) + self.assertEqual(str(t), "m0 *1.5 0,0") + + t = pya.CplxTrans() + t.disp = (1, 2) + self.assertEqual(str(t), "r0 *1 1,2") + + t = pya.ICplxTrans(15, 25) + self.assertEqual(t.to_s(dbu = 0.01), "r0 *1 0.15000,0.25000") + + + def test_2(self): + + t = pya.Trans(pya.Trans.M0, 1, 2) + self.assertEqual(str(t), "m0 1,2") + + t = pya.Trans(pya.Trans.M0, x = 1, y = 2) + self.assertEqual(str(t), "m0 1,2") + + t = pya.Trans(pya.Trans.M0, pya.Vector(1, 2)) + self.assertEqual(str(t), "m0 1,2") + + t = pya.Trans(pya.Trans.M0, u = pya.Vector(1, 2)) + self.assertEqual(str(t), "m0 1,2") + + t = pya.Trans(rot = 3, mirrx = True) + self.assertEqual(str(t), "m135 0,0") + + t = pya.Trans(rot = 3, mirrx = True, x = 1, y = 2) + self.assertEqual(str(t), "m135 1,2") + + t = pya.Trans(3, True, 1, 2) + self.assertEqual(str(t), "m135 1,2") + + t = pya.Trans(3, True, pya.Vector(1, 2)) + self.assertEqual(str(t), "m135 1,2") + + t = pya.Trans(rot = 3, mirrx = True, u = pya.Vector(1, 2)) + self.assertEqual(str(t), "m135 1,2") + + t = pya.Trans() + self.assertEqual(str(t), "r0 0,0") + + t = pya.Trans(pya.DTrans.M0) + self.assertEqual(str(t), "m0 0,0") + + t = pya.Trans(pya.DTrans.M0, 1, 2) + self.assertEqual(str(t), "m0 1,2") + + t = pya.Trans(pya.DTrans.M0, x = 1, y = 2) + self.assertEqual(str(t), "m0 1,2") + + t = pya.Trans(c = pya.DTrans.M0, x = 1, y = 2) + self.assertEqual(str(t), "m0 1,2") + + t = pya.Trans(pya.Vector(1, 2)) + self.assertEqual(str(t), "r0 1,2") + + t = pya.Trans(1, 2) + self.assertEqual(str(t), "r0 1,2") + + + def test_3(self): + + try: + t = pya.CplxTrans(1.5, 2.5) + t.to_s(dbu = "17") + self.assertEqual(True, False) + except Exception as ex: + self.assertEqual(str(ex), "Value cannot be converted to a floating-point value for argument #2 ('dbu') in CplxTrans.to_s") + + try: + t = pya.CplxTrans("17") + self.assertEqual(True, False) + except Exception as ex: + self.assertEqual(str(ex).find("No overload with matching arguments."), 0) + + try: + t = pya.CplxTrans(uu = 17) + self.assertEqual(True, False) + except Exception as ex: + self.assertEqual(str(ex).find("Can't match arguments."), 0) + + try: + t = pya.CplxTrans(u = "17") + self.assertEqual(True, False) + except Exception as ex: + self.assertEqual(str(ex).find("No overload with matching arguments."), 0) + + try: + t = pya.Trans("17") + self.assertEqual(True, False) + except Exception as ex: + self.assertEqual(str(ex).find("No overload with matching arguments."), 0) + + +# run unit tests +if __name__ == '__main__': + suite = unittest.TestSuite() + # NOTE: Use this instead of loadTestsfromTestCase to select a specific test: + # suite.addTest(KWArgsTest("test_26")) + suite = unittest.TestLoader().loadTestsFromTestCase(KWArgsTest) + + if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful(): + sys.exit(1) +