diff --git a/src/rba/rba/rba.cc b/src/rba/rba/rba.cc index 48e843f5c..bc40e237c 100644 --- a/src/rba/rba/rba.cc +++ b/src/rba/rba/rba.cc @@ -26,6 +26,7 @@ #include "gsiExpression.h" #include "gsiSignals.h" #include "gsiInspector.h" +#include "gsiVariantArgs.h" #include "tlString.h" #include "tlInternational.h" #include "tlException.h" @@ -163,6 +164,24 @@ RubyStackTraceProvider::stack_depth () } #endif +// ------------------------------------------------------------------- + +static inline int +num_args (const gsi::MethodBase *m) +{ + return int (m->end_arguments () - m->begin_arguments ()); +} + +static VALUE +get_kwarg (const gsi::ArgType &atype, VALUE kwargs) +{ + if (kwargs != Qnil) { + return rb_hash_lookup2 (kwargs, rb_id2sym (rb_intern (atype.spec ()->name ().c_str ())), Qnil); + } else { + return Qnil; + } +} + // ------------------------------------------------------------------- // The lookup table for the method overload resolution @@ -277,15 +296,19 @@ public: return m_methods.end (); } - const gsi::MethodBase *get_variant (int argc, VALUE *argv, bool block_given, bool is_ctor, bool is_static, bool is_const) const + const gsi::MethodBase *get_variant (int argc, VALUE *argv, VALUE kwargs, bool block_given, bool is_ctor, bool is_static, bool is_const) const { // caching can't work for arrays or hashes - in this case, give up - for (int i = 0; i < argc; ++i) { + bool nocache = (kwargs != Qnil); + + for (int i = 0; i < argc && ! nocache; ++i) { int t = TYPE (argv[i]); - if (t == T_ARRAY || t == T_HASH) { - return find_variant (argc, argv, block_given, is_ctor, is_static, is_const); - } + nocache = (t == T_ARRAY || t == T_HASH); + } + + if (nocache) { + return find_variant (argc, argv, kwargs, block_given, is_ctor, is_static, is_const); } // try to find the variant in the cache @@ -296,13 +319,79 @@ public: return v->second; } - const gsi::MethodBase *meth = find_variant (argc, argv, block_given, is_ctor, is_static, is_const); + const gsi::MethodBase *meth = find_variant (argc, argv, kwargs, block_given, is_ctor, is_static, is_const); m_variants[key] = meth; return meth; } private: - const gsi::MethodBase *find_variant (int argc, VALUE *argv, bool block_given, bool is_ctor, bool is_static, bool is_const) const + static bool + compatible_with_args (const gsi::MethodBase *m, int argc, VALUE kwargs) + { + int nargs = num_args (m); + + if (argc >= nargs) { + // no more arguments to consider + return argc == nargs && (kwargs == Qnil || rb_hash_size_num (kwargs) == 0); + } + + if (kwargs != Qnil) { + + int nkwargs = int (rb_hash_size_num (kwargs)); + int kwargs_taken = 0; + + while (argc < nargs) { + const gsi::ArgType &atype = m->begin_arguments () [argc]; + VALUE rb_arg = rb_hash_lookup2 (kwargs, rb_id2sym (rb_intern (atype.spec ()->name ().c_str ())), Qnil); + if (rb_arg == Qnil) { + 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, VALUE kwargs) + { + std::string res = m->to_string (); + if (compatible_with_args (m, argc, kwargs)) { + res += " " + tl::to_string (tr ("[match candidate]")); + } + return res; + } + + std::string + describe_overloads (int argc, VALUE kwargs) const + { + std::string res; + for (auto m = begin (); m != end (); ++m) { + res += std::string (" ") + describe_overload (*m, argc, kwargs) + "\n"; + } + return res; + } + + const gsi::MethodBase *find_variant (int argc, VALUE *argv, VALUE kwargs, bool block_given, bool is_ctor, bool is_static, bool is_const) const { // get number of candidates by argument count const gsi::MethodBase *meth = 0; @@ -334,7 +423,7 @@ private: // ignore callbacks - } else if ((*m)->compatible_with_num_args (argc)) { + } else if (compatible_with_args (*m, argc, kwargs)) { ++candidates; meth = *m; @@ -344,7 +433,7 @@ private: } // no method found, but the ctor was requested - implement that method as replacement for the default "initialize" - if (! meth && argc == 0 && is_ctor) { + if (! meth && argc == 0 && is_ctor && kwargs == Qnil) { return 0; } @@ -366,7 +455,7 @@ private: 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)); + throw tl::Exception (tl::to_string (tr ("Can't match arguments. Variants are:\n")) + describe_overloads (argc, kwargs)); } @@ -383,13 +472,16 @@ private: if (! (*m)->is_callback () && ! (*m)->is_signal ()) { // 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; - VALUE *av = argv; - for (gsi::MethodBase::argument_iterator a = (*m)->begin_arguments (); is_valid && av < argv + argc && a != (*m)->end_arguments (); ++a, ++av) { - if (test_arg (*a, *av, false /*strict*/)) { + int i = 0; + for (gsi::MethodBase::argument_iterator a = (*m)->begin_arguments (); is_valid && a != (*m)->end_arguments (); ++a, ++i) { + VALUE arg = i >= argc ? get_kwarg (*a, kwargs) : argv[i]; + if (arg == Qnil) { + is_valid = a->spec ()->has_default (); + } else if (test_arg (*a, arg, false /*strict*/)) { ++sc; - } else if (test_arg (*a, *av, true /*loose*/)) { + } else if (test_arg (*a, arg, true /*loose*/)) { // non-scoring match } else { is_valid = false; @@ -414,12 +506,17 @@ private: 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; @@ -434,11 +531,11 @@ private: } if (! meth) { - throw tl::Exception (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 (argc, kwargs)); } if (candidates > 1) { - throw tl::Exception (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 (argc, kwargs)); } if (is_const && ! meth->is_const ()) { @@ -943,16 +1040,64 @@ static gsi::ArgType create_void_type () static gsi::ArgType s_void_type = create_void_type (); -void -push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, VALUE *argv, int argc, tl::Heap &heap) +static int get_kwargs_keys (VALUE key, VALUE, VALUE arg) { - int i = 0; + std::set *names = reinterpret_cast *> (arg); + names->insert (ruby2c (key)); + + return ST_CONTINUE; +} + +void +push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, VALUE *argv, int argc, VALUE kwargs, tl::Heap &heap) +{ + int iarg = 0; + int kwargs_taken = 0; + int nkwargs = kwargs == Qnil ? 0 : int (rb_hash_size_num (kwargs)); try { - VALUE *av = argv; - for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && av < argv + argc; ++a, ++av, ++i) { - push_arg (*a, arglist, *av, heap); + for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a, ++iarg) { + + VALUE arg = iarg >= argc ? get_kwarg (*a, kwargs) : argv[iarg]; + if (arg == Qnil) { + 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, heap); + } + + } + + if (kwargs_taken != nkwargs) { + + // check if there are any left-over keyword parameters with unknown names + + std::set invalid_names; + rb_hash_foreach (kwargs, &get_kwargs_keys, reinterpret_cast (&invalid_names)); + + for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a) { + invalid_names.erase (a->spec ()->name ()); + } + + 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) { @@ -960,28 +1105,34 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, VALUE *argv, i // 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, 0, arglist, heap); + pull_arg (*a, 0, arglist, heap); } - const gsi::ArgSpecBase *arg_spec = meth->begin_arguments () [i].spec (); + if (iarg < num_args (meth)) { + + const gsi::ArgSpecBase *arg_spec = meth->begin_arguments () [iarg].spec (); + + std::string msg; + 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); + } + + tl::Exception new_ex (msg); + new_ex.set_first_chance (ex.first_chance ()); + throw new_ex; - std::string 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); + throw; } - tl::Exception new_ex (msg); - new_ex.set_first_chance (ex.first_chance ()); - throw new_ex; - } catch (...) { // 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, 0, arglist, heap); + pull_arg (*a, 0, arglist, heap); } throw; @@ -996,6 +1147,15 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) RBA_TRY + VALUE kwargs = Qnil; + bool check_last = true; +#if HAVE_RUBY_VERSION_CODE>=20700 + check_last = rb_keyword_given_p (); +#endif + if (check_last && argc > 0 && RB_TYPE_P (argv[argc - 1], T_HASH)) { + kwargs = argv[--argc]; + } + tl::Heap heap; const gsi::ClassBase *cls_decl; @@ -1027,7 +1187,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) } - const gsi::MethodBase *meth = mt->entry (mid).get_variant (argc, argv, rb_block_given_p (), ctor, p == 0, p != 0 && p->const_ref ()); + const gsi::MethodBase *meth = mt->entry (mid).get_variant (argc, argv, kwargs, rb_block_given_p (), ctor, p == 0, p != 0 && p->const_ref ()); if (! meth) { @@ -1070,7 +1230,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) { gsi::SerialArgs arglist (meth->argsize ()); - push_args (arglist, meth, argv, argc, heap); + push_args (arglist, meth, argv, argc, kwargs, heap); meth->call (0, arglist, retlist); } @@ -1124,7 +1284,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) { gsi::SerialArgs arglist (meth->argsize ()); - push_args (arglist, meth, argv, argc, heap); + push_args (arglist, meth, argv, argc, kwargs, heap); meth->call (obj, arglist, retlist); } @@ -1143,7 +1303,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) rr.reset (); iter->get (rr); - VALUE value = pop_arg (meth->ret_type (), p, rr, heap); + VALUE value = pull_arg (meth->ret_type (), p, rr, heap); rba_yield_checked (value); iter->inc (); @@ -1163,7 +1323,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor) } else { - ret = pop_arg (meth->ret_type (), p, retlist, heap); + ret = pull_arg (meth->ret_type (), p, retlist, heap); } @@ -1834,7 +1994,7 @@ public: gsi::SerialArgs arglist (c->meth->argsize ()); c->meth->call (0, arglist, retlist); tl::Heap heap; - VALUE ret = pop_arg (c->meth->ret_type (), 0, retlist, heap); + VALUE ret = pull_arg (c->meth->ret_type (), 0, retlist, heap); rb_define_const (c->klass, c->name.c_str (), ret); } catch (tl::Exception &ex) { diff --git a/src/rba/rba/rbaInspector.cc b/src/rba/rba/rbaInspector.cc index 6a6926b06..1359d77c5 100644 --- a/src/rba/rba/rbaInspector.cc +++ b/src/rba/rba/rbaInspector.cc @@ -416,7 +416,7 @@ public: meth->call (obj, arglist, retlist); tl::Heap heap; - return pop_arg (meth->ret_type (), p, retlist, heap); + return pull_arg (meth->ret_type (), p, retlist, heap); } diff --git a/src/rba/rba/rbaInternal.cc b/src/rba/rba/rbaInternal.cc index 6f51b83b2..cc3399598 100644 --- a/src/rba/rba/rbaInternal.cc +++ b/src/rba/rba/rbaInternal.cc @@ -250,7 +250,7 @@ Proxy::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) { - rb_ary_push (argv, pop_arg (*a, 0, args, heap)); + rb_ary_push (argv, pull_arg (*a, 0, args, heap)); } VALUE rb_ret = rba_funcall2_checked (m_self, mid, RARRAY_LEN (argv), RARRAY_PTR (argv)); @@ -827,7 +827,7 @@ void SignalHandler::call (const gsi::MethodBase *meth, gsi::SerialArgs &args, gs // TODO: signals with default arguments? for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) { - rb_ary_push (argv, pop_arg (*a, 0, args, heap)); + rb_ary_push (argv, pull_arg (*a, 0, args, heap)); } // call the signal handlers ... the last one will deliver the return value diff --git a/src/rba/rba/rbaMarshal.cc b/src/rba/rba/rbaMarshal.cc index d7ee41c18..4d73580a6 100644 --- a/src/rba/rba/rbaMarshal.cc +++ b/src/rba/rba/rbaMarshal.cc @@ -34,7 +34,7 @@ namespace rba { -void push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, VALUE *argv, int argc, tl::Heap &heap); +void push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, VALUE *argv, int argc, VALUE kwargs, tl::Heap &heap); // ------------------------------------------------------------------- // Serialization adaptors for strings, variants, vectors and maps @@ -521,7 +521,7 @@ struct writer gsi::SerialArgs retlist (meth->retsize ()); gsi::SerialArgs arglist (meth->argsize ()); - push_args (arglist, meth, RARRAY_PTR (arg), n, *heap); + push_args (arglist, meth, RARRAY_PTR (arg), n, Qnil, *heap); meth->call (0, arglist, retlist); @@ -1026,10 +1026,10 @@ size_t RubyBasedMapAdaptor::serial_size () const } // ------------------------------------------------------------------- -// Pops an argument from the call or return stack +// Pulls an argument from the front of an argument queue VALUE -pop_arg (const gsi::ArgType &atype, Proxy *self, gsi::SerialArgs &aserial, tl::Heap &heap) +pull_arg (const gsi::ArgType &atype, Proxy *self, gsi::SerialArgs &aserial, tl::Heap &heap) { VALUE ret = Qnil; gsi::do_on_type () (atype.type (), &aserial, &ret, self, atype, &heap); diff --git a/src/rba/rba/rbaMarshal.h b/src/rba/rba/rbaMarshal.h index 7fd69fb90..4b4bba50c 100644 --- a/src/rba/rba/rbaMarshal.h +++ b/src/rba/rba/rbaMarshal.h @@ -41,7 +41,7 @@ class Proxy; * * "self" is a reference to the object that the method is called on or 0 if there is no such object. */ -VALUE pop_arg (const gsi::ArgType &atype, Proxy *self, gsi::SerialArgs &aserial, tl::Heap &heap); +VALUE pull_arg (const gsi::ArgType &atype, Proxy *self, gsi::SerialArgs &aserial, tl::Heap &heap); /** * @brief Pushes an argument on the call or return stack diff --git a/src/rba/unit_tests/rbaTests.cc b/src/rba/unit_tests/rbaTests.cc index b5c5f4162..16a3b7fd6 100644 --- a/src/rba/unit_tests/rbaTests.cc +++ b/src/rba/unit_tests/rbaTests.cc @@ -91,6 +91,7 @@ void run_rubytest (tl::TestBase * /*_this*/, const std::string &fn) #define RUBYTEST(n, file) \ TEST(n) { run_rubytest(_this, file); } +RUBYTEST (kwargsTest, "kwargs.rb") RUBYTEST (antTest, "antTest.rb") RUBYTEST (dbBooleanTest, "dbBooleanTest.rb") RUBYTEST (dbBoxTest, "dbBoxTest.rb") diff --git a/testdata/ruby/kwargs.rb b/testdata/ruby/kwargs.rb new file mode 100644 index 000000000..9a52cfc06 --- /dev/null +++ b/testdata/ruby/kwargs.rb @@ -0,0 +1,216 @@ +# encoding: UTF-8 + +# 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 + +if !$:.member?(File::dirname($0)) + $:.push(File::dirname($0)) +end + +load("test_prologue.rb") + + +# NOTE: RBA::CplxTrans and RBA::Trans and good test cases +# for the keyword arguments feature + +class KWArgs_TestClass < TestBase + + def test_1 + + t = RBA::CplxTrans::new() + assert_equal(t.to_s, "r0 *1 0,0") + + t = RBA::CplxTrans::new(1.5) + assert_equal(t.to_s, "r0 *1.5 0,0") + + t = RBA::CplxTrans::new(1, 2) + assert_equal(t.to_s, "r0 *1 1,2") + + t = RBA::CplxTrans::new(1, y: 2) + assert_equal(t.to_s, "r0 *1 1,2") + + t = RBA::CplxTrans::new(x: 1, y: 2) + assert_equal(t.to_s, "r0 *1 1,2") + + t = RBA::CplxTrans::new(u: RBA::DVector::new(1, 2)) + assert_equal(t.to_s, "r0 *1 1,2") + + t = RBA::CplxTrans::new(RBA::DVector::new(1, 2)) + assert_equal(t.to_s, "r0 *1 1,2") + + t = RBA::CplxTrans::new(u: RBA::Vector::new(1, 2)) + assert_equal(t.to_s, "r0 *1 1,2") + + t = RBA::CplxTrans::new(u: [1, 2]) + assert_equal(t.to_s, "r0 *1 1,2") + + t = RBA::CplxTrans::new(mag: 1.5) + assert_equal(t.to_s, "r0 *1.5 0,0") + + t = RBA::CplxTrans::new(1.5, 45, true, 1, 2) + assert_equal(t.to_s, "m22.5 *1.5 1,2") + + t = RBA::CplxTrans::new(1.5, 45, true, RBA::DVector::new(1, 2)) + assert_equal(t.to_s, "m22.5 *1.5 1,2") + + t = RBA::CplxTrans::new(1.5, x: 1, y: 2, mirrx: true, rot: 45) + assert_equal(t.to_s, "m22.5 *1.5 1,2") + + t = RBA::CplxTrans::new(RBA::CplxTrans::M0) + assert_equal(t.to_s, "m0 *1 0,0") + + t = RBA::CplxTrans::new(RBA::CplxTrans::M0, u: RBA::DVector::new(1, 2)) + assert_equal(t.to_s, "m0 *1 1,2") + + t = RBA::CplxTrans::new(RBA::CplxTrans::M0, mag: 1.5, u: RBA::DVector::new(1, 2)) + assert_equal(t.to_s, "m0 *1.5 1,2") + + t = RBA::CplxTrans::new(RBA::CplxTrans::M0, 1.5, RBA::DVector::new(1, 2)) + assert_equal(t.to_s, "m0 *1.5 1,2") + + t = RBA::CplxTrans::new(RBA::CplxTrans::M0, mag: 1.5, x: 1, y: 2) + assert_equal(t.to_s, "m0 *1.5 1,2") + + t = RBA::CplxTrans::new(RBA::CplxTrans::M0, 1.5, 1, 2) + assert_equal(t.to_s, "m0 *1.5 1,2") + + t = RBA::CplxTrans::new(RBA::VCplxTrans::M0) + assert_equal(t.to_s, "m0 *1 0,0") + + t = RBA::CplxTrans::new(RBA::ICplxTrans::M0) + assert_equal(t.to_s, "m0 *1 0,0") + + t = RBA::CplxTrans::new(RBA::DCplxTrans::M0) + assert_equal(t.to_s, "m0 *1 0,0") + + t = RBA::CplxTrans::new(RBA::Trans::M0) + assert_equal(t.to_s, "m0 *1 0,0") + + t = RBA::CplxTrans::new(RBA::Trans::M0, 1.5) + assert_equal(t.to_s, "m0 *1.5 0,0") + + t = RBA::CplxTrans::new(RBA::Trans::M0, mag: 1.5) + assert_equal(t.to_s, "m0 *1.5 0,0") + + t = RBA::CplxTrans::new(t: RBA::Trans::M0, mag: 1.5) + assert_equal(t.to_s, "m0 *1.5 0,0") + + t = RBA::CplxTrans::new + t.disp = [1, 2] + assert_equal(t.to_s, "r0 *1 1,2") + + t = RBA::ICplxTrans::new(15, 25) + assert_equal(t.to_s(dbu: 0.01), "r0 *1 0.15000,0.25000") + + end + + def test_2 + + t = RBA::Trans::new(RBA::Trans::M0, 1, 2) + assert_equal(t.to_s, "m0 1,2") + + t = RBA::Trans::new(RBA::Trans::M0, x: 1, y: 2) + assert_equal(t.to_s, "m0 1,2") + + t = RBA::Trans::new(RBA::Trans::M0, RBA::Vector::new(1, 2)) + assert_equal(t.to_s, "m0 1,2") + + t = RBA::Trans::new(RBA::Trans::M0, u: RBA::Vector::new(1, 2)) + assert_equal(t.to_s, "m0 1,2") + + t = RBA::Trans::new(rot: 3, mirrx: true) + assert_equal(t.to_s, "m135 0,0") + + t = RBA::Trans::new(rot: 3, mirrx: true, x: 1, y: 2) + assert_equal(t.to_s, "m135 1,2") + + t = RBA::Trans::new(3, true, 1, 2) + assert_equal(t.to_s, "m135 1,2") + + t = RBA::Trans::new(3, true, RBA::Vector::new(1, 2)) + assert_equal(t.to_s, "m135 1,2") + + t = RBA::Trans::new(rot: 3, mirrx: true, u: RBA::Vector::new(1, 2)) + assert_equal(t.to_s, "m135 1,2") + + t = RBA::Trans::new + assert_equal(t.to_s, "r0 0,0") + + t = RBA::Trans::new(RBA::DTrans::M0) + assert_equal(t.to_s, "m0 0,0") + + t = RBA::Trans::new(RBA::DTrans::M0, 1, 2) + assert_equal(t.to_s, "m0 1,2") + + t = RBA::Trans::new(RBA::DTrans::M0, x: 1, y: 2) + assert_equal(t.to_s, "m0 1,2") + + t = RBA::Trans::new(c: RBA::DTrans::M0, x: 1, y: 2) + assert_equal(t.to_s, "m0 1,2") + + t = RBA::Trans::new(RBA::Vector::new(1, 2)) + assert_equal(t.to_s, "r0 1,2") + + t = RBA::Trans::new(1, 2) + assert_equal(t.to_s, "r0 1,2") + + end + + def test_3 + + begin + t = RBA::CplxTrans::new(1.5, 2.5) + t.to_s(dbu: "17") + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "TypeError: no implicit conversion to float from string for argument #2 ('dbu') in CplxTrans::to_s") + end + + begin + t = RBA::CplxTrans::new("17") + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s.index("No overload with matching arguments."), 0) + end + + begin + t = RBA::CplxTrans::new(uu: 17) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s.index("Can't match arguments."), 0) + end + + begin + t = RBA::CplxTrans::new(u: "17") + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s.index("No overload with matching arguments."), 0) + end + + begin + t = RBA::Trans::new("17") + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s.index("No overload with matching arguments."), 0) + end + + end + +end + +load("test_epilogue.rb") +