WIP: keyword arguments for Ruby

This commit is contained in:
Matthias Koefferlein 2023-12-27 22:17:39 +01:00
parent 702bcbe924
commit 8f9b904d87
7 changed files with 431 additions and 54 deletions

View File

@ -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<std::string> *names = reinterpret_cast<std::set<std::string> *> (arg);
names->insert (ruby2c<std::string> (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<std::string> invalid_names;
rb_hash_foreach (kwargs, &get_kwargs_keys, reinterpret_cast<VALUE> (&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) {

View File

@ -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);
}

View File

@ -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

View File

@ -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::ObjectType>
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<reader> () (atype.type (), &aserial, &ret, self, atype, &heap);

View File

@ -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

View File

@ -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")

216
testdata/ruby/kwargs.rb vendored Normal file
View File

@ -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")