diff --git a/src/ant/ant/antObject.cc b/src/ant/ant/antObject.cc index 3a299e9ff..abdf04b2c 100644 --- a/src/ant/ant/antObject.cc +++ b/src/ant/ant/antObject.cc @@ -431,7 +431,7 @@ public: // .. nothing yet .. } - void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &vv) const + void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &vv, const std::map * /*kwargs*/) const { if (vv.size () != 0) { throw tl::EvalError (tl::to_string (tr ("Annotation function must not have arguments")), context); diff --git a/src/db/db/dbLayoutQuery.cc b/src/db/db/dbLayoutQuery.cc index 2a3153340..1b9c4ae5b 100644 --- a/src/db/db/dbLayoutQuery.cc +++ b/src/db/db/dbLayoutQuery.cc @@ -2194,7 +2194,7 @@ public: // .. nothing yet .. } - void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &args) const + void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &args, const std::map * /*kwargs*/) const { if (args.size () > 0) { throw tl::EvalError (tl::to_string (tr ("Query function does not allow parameters")), context); diff --git a/src/db/db/dbLoadLayoutOptions.cc b/src/db/db/dbLoadLayoutOptions.cc index ae8b4b4dd..8618103fc 100644 --- a/src/db/db/dbLoadLayoutOptions.cc +++ b/src/db/db/dbLoadLayoutOptions.cc @@ -133,7 +133,7 @@ namespace db args.push_back (value); } tl::ExpressionParserContext context; - ref.user_cls ()->eval_cls ()->execute (context, out, ref, m, args); + ref.user_cls ()->eval_cls ()->execute (context, out, ref, m, args, 0); ref = out; @@ -160,7 +160,7 @@ namespace db std::vector args; tl::ExpressionParserContext context; - ref.user_cls ()->eval_cls ()->execute (context, out, ref, m, args); + ref.user_cls ()->eval_cls ()->execute (context, out, ref, m, args, 0); ref = out; diff --git a/src/db/db/dbSaveLayoutOptions.cc b/src/db/db/dbSaveLayoutOptions.cc index d90030027..0207e4abb 100644 --- a/src/db/db/dbSaveLayoutOptions.cc +++ b/src/db/db/dbSaveLayoutOptions.cc @@ -138,7 +138,7 @@ SaveLayoutOptions::set_option_by_name (const std::string &method, const tl::Vari tl::Variant out; std::vector args; args.push_back (value); - eval_cls->execute (context, out, options_ref, method + "=", args); + eval_cls->execute (context, out, options_ref, method + "=", args, 0); } tl::Variant @@ -151,7 +151,7 @@ SaveLayoutOptions::get_option_by_name (const std::string &method) tl::Variant out; std::vector args; - eval_cls->execute (context, out, options_ref, method, args); + eval_cls->execute (context, out, options_ref, method, args, 0); return out; } diff --git a/src/db/db/dbTilingProcessor.cc b/src/db/db/dbTilingProcessor.cc index da234d4cf..93f99c65a 100644 --- a/src/db/db/dbTilingProcessor.cc +++ b/src/db/db/dbTilingProcessor.cc @@ -487,7 +487,7 @@ public: // .. nothing yet .. } - void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant &out, const std::vector &args) const + void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant &out, const std::vector &args, const std::map * /*kwargs*/) const { out = mp_proc->receiver (args); } @@ -506,7 +506,7 @@ public: // .. nothing yet .. } - void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant & /*out*/, const std::vector &args) const + void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant & /*out*/, const std::vector &args, const std::map * /*kwargs*/) const { mp_proc->put (m_ix, m_iy, m_tile_box, args); } @@ -526,7 +526,7 @@ public: // .. nothing yet .. } - void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant & /*out*/, const std::vector & /*args*/) const + void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant & /*out*/, const std::vector & /*args*/, const std::map * /*kwargs*/) const { // TODO: ... implement .. } diff --git a/src/gsi/gsi/gsiClass.h b/src/gsi/gsi/gsiClass.h index 4e6699042..a9a87ea7d 100644 --- a/src/gsi/gsi/gsiClass.h +++ b/src/gsi/gsi/gsiClass.h @@ -84,7 +84,7 @@ template struct _var_user_to_string_impl; template struct _var_user_to_string_impl { - static std::string call (const T *a, const VariantUserClassImpl * /*delegate*/) { return a->to_string (); } + static std::string call (const T *a, const VariantUserClassImpl * /*delegate*/) { return a ? a->to_string () : std::string (); } }; template diff --git a/src/gsi/gsi/gsiExpression.cc b/src/gsi/gsi/gsiExpression.cc index 2c2980c6e..fd8b7fde3 100644 --- a/src/gsi/gsi/gsiExpression.cc +++ b/src/gsi/gsi/gsiExpression.cc @@ -266,9 +266,15 @@ public: // .. nothing yet .. } - void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant &out, const std::vector &args) const + bool supports_keyword_parameters () const { - if (! args.empty ()) { + // for future extensions + return true; + } + + void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant &out, const std::vector &args, const std::map *kwargs) const + { + if (! args.empty () || kwargs) { throw tl::Exception (tl::to_string (tr ("Class '%s' is not a function - use 'new' to create a new object")), mp_var_cls->name ()); } out = tl::Variant ((void *) 0, mp_var_cls, false); @@ -532,12 +538,12 @@ VariantUserClassImpl::to_double_impl (void *obj) const } void -VariantUserClassImpl::execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const +VariantUserClassImpl::execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs) const { if (mp_object_cls == 0 && method == "is_a") { - if (args.size () != 1) { - throw tl::EvalError (tl::to_string (tr ("'is_a' method requires exactly one argument")), context); + if (args.size () != 1 || kwargs) { + throw tl::EvalError (tl::to_string (tr ("'is_a' method requires exactly one argument (no keyword arguments)")), context); } bool ret = false; @@ -550,7 +556,7 @@ VariantUserClassImpl::execute (const tl::ExpressionParserContext &context, tl::V out = ret; - } else if (mp_object_cls != 0 && method == "new" && args.size () == 0) { + } else if (mp_object_cls != 0 && method == "new" && args.size () == 0 && ! kwargs) { void *obj = mp_cls->create (); if (obj) { @@ -574,8 +580,8 @@ VariantUserClassImpl::execute (const tl::ExpressionParserContext &context, tl::V } else if (mp_object_cls == 0 && method == "dup") { - if (args.size () != 0) { - throw tl::EvalError (tl::to_string (tr ("'dup' method does not allow arguments")), context); + if (args.size () != 0 || kwargs) { + throw tl::EvalError (tl::to_string (tr ("'dup' method does not allow arguments (no keyword arguments)")), context); } void *obj = mp_cls->create (); @@ -602,7 +608,7 @@ VariantUserClassImpl::execute (const tl::ExpressionParserContext &context, tl::V } else { try { - execute_gsi (context, out, object, method, args); + execute_gsi (context, out, object, method, args, kwargs); } catch (tl::EvalError &) { throw; } catch (tl::Exception &ex) { @@ -709,8 +715,92 @@ static const gsi::ClassBase *find_class_scope (const gsi::ClassBase *cls, const return 0; } +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, const std::map *kwargs) +{ + int nargs = num_args (m); + + if (argc >= nargs) { + // no more arguments to consider + return argc == nargs && (! kwargs || kwargs->empty ()); + } + + if (kwargs) { + + int nkwargs = int (kwargs->size ()); + int kwargs_taken = 0; + + while (argc < nargs) { + const gsi::ArgType &atype = m->begin_arguments () [argc]; + auto i = kwargs->find (atype.spec ()->name ()); + if (i == kwargs->end ()) { + 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, const std::map *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 ExpressionMethodTable *mt, int mid, int argc, const std::map *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 const tl::Variant * +get_kwarg (const gsi::ArgType &atype, const std::map *kwargs) +{ + if (kwargs) { + auto i = kwargs->find (atype.spec ()->name ()); + if (i != kwargs->end ()) { + return &i->second; + } + } + return 0; +} + void -VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context*/, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const +VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context*/, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs) const { tl_assert (object.is_user ()); @@ -762,7 +852,7 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context throw tl::Exception (tl::sprintf (tl::to_string (tr ("Signals are not supported inside expressions (event %s)")), method.c_str ())); } else if ((*m)->is_callback()) { // ignore callbacks - } else if ((*m)->compatible_with_num_args ((unsigned int) args.size ())) { + } else if (compatible_with_args (*m, int (args.size ()), kwargs)) { ++candidates; meth = *m; } @@ -771,20 +861,7 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context // no candidate -> error if (! meth) { - - std::set nargs; - for (ExpressionMethodTableEntry::method_iterator m = mt->begin (mid); m != mt->end (mid); ++m) { - 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 for method %s, class %s (got %d, expected %s)")), method.c_str (), mp_cls->name (), int (args.size ()), nargs_s)); + throw tl::Exception (tl::to_string (tr ("Can't match arguments. Variants are:\n")) + describe_overloads (mt, mid, int (args.size ()), kwargs)); } // more than one candidate -> refine by checking the arguments @@ -800,13 +877,16 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context if (! (*m)->is_callback () && ! (*m)->is_signal ()) { // check arguments (count and type) - bool is_valid = (*m)->compatible_with_num_args ((unsigned int) args.size ()); + bool is_valid = compatible_with_args (*m, (int) args.size (), kwargs); 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 (gsi::test_arg (*a, args [i], false /*strict*/)) { + for (gsi::MethodBase::argument_iterator a = (*m)->begin_arguments (); is_valid && a != (*m)->end_arguments (); ++a, ++i) { + const tl::Variant *arg = i >= int (args.size ()) ? get_kwarg (*a, kwargs) : &args[i]; + if (! arg) { + is_valid = a->spec ()->has_default (); + } else if (gsi::test_arg (*a, *arg, false /*strict*/)) { ++sc; - } else if (test_arg (*a, args [i], true /*loose*/)) { + } else if (test_arg (*a, *arg, true /*loose*/)) { // non-scoring match } else { is_valid = false; @@ -831,12 +911,17 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context 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; @@ -851,11 +936,11 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context } if (! meth) { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("No method with matching arguments for method %s, class %s")), method.c_str (), mp_cls->name ())); + throw tl::Exception (tl::to_string (tr ("No overload with matching arguments. Variants are:\n")) + describe_overloads (mt, mid, int (args.size ()), kwargs)); } if (candidates > 1) { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Ambiguous overload variants for method %s, class %s - multiple method declarations match arguments")), method.c_str (), mp_cls->name ())); + throw tl::Exception (tl::to_string (tr ("Ambiguous overload variants - multiple method declarations match arguments. Variants are:\n")) + describe_overloads (mt, mid, int (args.size ()), kwargs)); } if (m_is_const && ! meth->is_const ()) { @@ -869,22 +954,76 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context } else if (meth->smt () != gsi::MethodBase::None) { + if (kwargs) { + throw tl::Exception (tl::to_string (tr ("Keyword arguments not permitted"))); + } + out = special_method_impl (meth->smt (), object, args); } else { gsi::SerialArgs arglist (meth->argsize ()); tl::Heap heap; - size_t narg = 0; - for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && narg < args.size (); ++a, ++narg) { + + int iarg = 0; + int kwargs_taken = 0; + int nkwargs = kwargs ? int (kwargs->size ()) : 0; + + for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a, ++iarg) { + try { - // Note: this const_cast is ugly, but it will basically enable "out" parameters - // TODO: clean this up. - gsi::push_arg (arglist, *a, const_cast (args [narg]), &heap); + + const tl::Variant *arg = iarg >= int (args.size ()) ? get_kwarg (*a, kwargs) : &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 >= int (args.size ())) { + ++kwargs_taken; + } + // Note: this const_cast is ugly, but it will basically enable "out" parameters + // TODO: clean this up. + gsi::push_arg (arglist, *a, const_cast (*arg), &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); } + + } + + if (kwargs_taken != nkwargs) { + + // check if there are any left-over keyword parameters with unknown names + + 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 (auto i = kwargs->begin (); i != kwargs->end (); ++i) { + if (valid_names.find (i->first) == valid_names.end ()) { + invalid_names.insert (i->first); + } + } + + 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 ()); + } + } SerialArgs retlist (meth->retsize ()); diff --git a/src/gsi/gsi/gsiExpression.h b/src/gsi/gsi/gsiExpression.h index 4e70e93e9..d0876b93a 100644 --- a/src/gsi/gsi/gsiExpression.h +++ b/src/gsi/gsi/gsiExpression.h @@ -55,7 +55,7 @@ public: int to_int_impl (void *) const; double to_double_impl (void *) const; - virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const; + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs) const; void initialize (const gsi::ClassBase *cls, const tl::VariantUserClassBase *self, const tl::VariantUserClassBase *object_cls, bool is_const); @@ -64,7 +64,7 @@ private: const tl::VariantUserClassBase *mp_self, *mp_object_cls; bool m_is_const; - virtual void execute_gsi (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const; + virtual void execute_gsi (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs = 0) const; bool has_method (const std::string &method) const; }; diff --git a/src/gsi/unit_tests/gsiExpressionTests.cc b/src/gsi/unit_tests/gsiExpressionTests.cc index 1bfdf1013..58a393ef2 100644 --- a/src/gsi/unit_tests/gsiExpressionTests.cc +++ b/src/gsi/unit_tests/gsiExpressionTests.cc @@ -564,7 +564,7 @@ class CollectFunction : public tl::EvalFunction { public: - virtual void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant &out, const std::vector &args) const + virtual void execute (const tl::ExpressionParserContext & /*context*/, tl::Variant &out, const std::vector &args, const std::map * /*kwargs*/) const { out = tl::Variant (); if (args.size () > 0) { @@ -623,3 +623,32 @@ TEST(11) v = e.parse ("var b = Trans.new(1)*Trans.new(Vector.new(10, 20))").execute (); EXPECT_EQ (v.to_string (), std::string ("r90 -20,10")); } + +TEST(12) +{ + // Keyword arguments are best tested on transformations, here CplxTrans + + tl::Eval e; + tl::Variant v; + + v = e.parse ("var t = CplxTrans.new()").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1 0,0")); + v = e.parse ("var t = CplxTrans.new(1.5)").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1.5 0,0")); + v = e.parse ("var t = CplxTrans.new(1, 2)").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1 1,2")); + v = e.parse ("var t = CplxTrans.new(1, y=2)").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1 1,2")); + v = e.parse ("var t = CplxTrans.new(x=1, y=2)").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1 1,2")); + v = e.parse ("var t = CplxTrans.new(u=DVector.new(1, 2))").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1 1,2")); + v = e.parse ("var t = CplxTrans.new(DVector.new(1, 2))").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1 1,2")); + v = e.parse ("var t = CplxTrans.new(u=Vector.new(1, 2))").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1 1,2")); + v = e.parse ("var t = CplxTrans.new(u=[1, 2])").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1 1,2")); + v = e.parse ("var t = CplxTrans.new(mag=1.5)").execute (); + EXPECT_EQ (v.to_string (), std::string ("r0 *1.5 0,0")); +} diff --git a/src/laybasic/laybasic/layLayerProperties.cc b/src/laybasic/laybasic/layLayerProperties.cc index 3175306f5..73d5fb249 100644 --- a/src/laybasic/laybasic/layLayerProperties.cc +++ b/src/laybasic/laybasic/layLayerProperties.cc @@ -436,7 +436,7 @@ public: // .. nothing yet .. } - void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &vv) const + void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &vv, const std::map * /*kwargs*/) const { if (vv.size () != 0) { throw tl::EvalError (tl::to_string (tr ("Layer source function must not have arguments")), context); diff --git a/src/rba/rba/rba.cc b/src/rba/rba/rba.cc index 04e0f37f2..990c6c8db 100644 --- a/src/rba/rba/rba.cc +++ b/src/rba/rba/rba.cc @@ -531,11 +531,11 @@ private: } if (! meth) { - throw tl::TypeError (tl::to_string (tr ("No overload with matching arguments. Variants are:\n")) + describe_overloads (argc, kwargs)); + throw tl::Exception (tl::to_string (tr ("No overload with matching arguments. Variants are:\n")) + describe_overloads (argc, kwargs)); } if (candidates > 1) { - throw tl::TypeError (tl::to_string (tr ("Ambiguous overload variants - multiple method declarations match arguments. Variants are:\n")) + describe_overloads (argc, kwargs)); + throw tl::Exception (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 ()) { diff --git a/src/tl/tl/tlExpression.cc b/src/tl/tl/tlExpression.cc index ec3757f34..eed1cc73a 100644 --- a/src/tl/tl/tlExpression.cc +++ b/src/tl/tl/tlExpression.cc @@ -306,12 +306,12 @@ class TL_PUBLIC ListClass : public EvalClass { public: - void execute (const ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const + void execute (const ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs) const { if (method == "push") { - if (args.size () != 1) { - throw EvalError (tl::to_string (tr ("'push' method expects one argument")), context); + if (args.size () != 1 || kwargs) { + throw EvalError (tl::to_string (tr ("'push' method expects one argument (keyword arguments not permitted)")), context); } object.push (args [0]); @@ -319,7 +319,7 @@ public: } else if (method == "size") { - if (args.size () != 0) { + if (args.size () != 0 || kwargs) { throw EvalError (tl::to_string (tr ("'size' method does not accept an argument")), context); } @@ -343,12 +343,12 @@ class TL_PUBLIC ArrayClass : public EvalClass { public: - void execute (const ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const + void execute (const ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs) const { if (method == "insert") { - if (args.size () != 2) { - throw EvalError (tl::to_string (tr ("'insert' method expects two arguments")), context); + if (args.size () != 2 || kwargs) { + throw EvalError (tl::to_string (tr ("'insert' method expects two arguments (keyword arguments not permitted)")), context); } object.insert (args [0], args [1]); @@ -356,7 +356,7 @@ public: } else if (method == "size") { - if (args.size () != 0) { + if (args.size () != 0 || kwargs) { throw EvalError (tl::to_string (tr ("'size' method does not accept an argument")), context); } @@ -364,7 +364,7 @@ public: } else if (method == "keys") { - if (args.size () != 0) { + if (args.size () != 0 || kwargs) { throw EvalError (tl::to_string (tr ("'keys' method does not accept an argument")), context); } @@ -375,7 +375,7 @@ public: } else if (method == "values") { - if (args.size () != 0) { + if (args.size () != 0 || kwargs) { throw EvalError (tl::to_string (tr ("'keys' method does not accept an argument")), context); } @@ -411,7 +411,7 @@ ExpressionNode::ExpressionNode (const ExpressionParserContext &context, size_t c } ExpressionNode::ExpressionNode (const ExpressionNode &other, const tl::Expression *expr) - : m_context (other.m_context) + : m_context (other.m_context), m_name (other.m_name) { m_context.set_expr (expr); m_c.reserve (other.m_c.size ()); @@ -517,7 +517,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "<", vv); + c->execute (m_context, o, v.get (), "<", vv, 0); v.swap (o); } else { @@ -567,7 +567,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "<=", vv); + c->execute (m_context, o, v.get (), "<=", vv, 0); v.swap (o); } else { @@ -617,7 +617,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), ">", vv); + c->execute (m_context, o, v.get (), ">", vv, 0); v.swap (o); } else { @@ -667,7 +667,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), ">=", vv); + c->execute (m_context, o, v.get (), ">=", vv, 0); v.swap (o); } else { @@ -717,7 +717,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "==", vv); + c->execute (m_context, o, v.get (), "==", vv, 0); v.swap (o); } else { @@ -767,7 +767,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "!=", vv); + c->execute (m_context, o, v.get (), "!=", vv, 0); v.swap (o); } else { @@ -817,7 +817,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "~", vv); + c->execute (m_context, o, v.get (), "~", vv, 0); v.swap (o); mp_eval->match_substrings ().clear (); @@ -914,7 +914,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "!~", vv); + c->execute (m_context, o, v.get (), "!~", vv, 0); v.swap (o); } else { @@ -1085,7 +1085,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "<<", vv); + c->execute (m_context, o, v.get (), "<<", vv, 0); v.swap (o); } else if (v->is_longlong ()) { @@ -1141,7 +1141,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), ">>", vv); + c->execute (m_context, o, v.get (), ">>", vv, 0); v.swap (o); } else if (v->is_longlong ()) { @@ -1197,7 +1197,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "+", vv); + c->execute (m_context, o, v.get (), "+", vv, 0); v.swap (o); } else if (v->is_a_string () || b->is_a_string ()) { @@ -1259,7 +1259,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "-", vv); + c->execute (m_context, o, v.get (), "-", vv, 0); v.swap (o); } else if (v->is_double () || b->is_double ()) { @@ -1319,7 +1319,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "*", vv); + c->execute (m_context, o, v.get (), "*", vv, 0); v.swap (o); } else if (v->is_a_string ()) { @@ -1409,7 +1409,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "/", vv); + c->execute (m_context, o, v.get (), "/", vv, 0); v.swap (o); } else if (v->is_double () || b->is_double ()) { @@ -1493,7 +1493,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "%", vv); + c->execute (m_context, o, v.get (), "%", vv, 0); v.swap (o); } else if (v->is_ulonglong () || b->is_ulonglong ()) { @@ -1565,7 +1565,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "&", vv); + c->execute (m_context, o, v.get (), "&", vv, 0); v.swap (o); } else if (v->is_ulonglong () || b->is_ulonglong ()) { @@ -1621,7 +1621,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "|", vv); + c->execute (m_context, o, v.get (), "|", vv, 0); v.swap (o); } else if (v->is_ulonglong () || b->is_ulonglong ()) { @@ -1677,7 +1677,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*b); - c->execute (m_context, o, v.get (), "^", vv); + c->execute (m_context, o, v.get (), "^", vv, 0); v.swap (o); } else if (v->is_ulonglong () || b->is_ulonglong ()) { @@ -1733,7 +1733,7 @@ public: tl::Variant o; std::vector vv; vv.push_back (*e); - c->execute (m_context, o, v.get (), "[]", vv); + c->execute (m_context, o, v.get (), "[]", vv, 0); v.swap (o); } else if (v->is_list ()) { @@ -2025,11 +2025,17 @@ public: m_c[0]->execute (v); std::vector vv; + std::map kwargs; + vv.reserve (m_c.size () - 1); for (std::vector::const_iterator c = m_c.begin () + 1; c != m_c.end (); ++c) { EvalTarget a; (*c)->execute (a); - vv.push_back (*a); + if (! (*c)->name ().empty ()) { + kwargs [(*c)->name ()] = *a; + } else { + vv.push_back (*a); + } } const EvalClass *c = 0; @@ -2048,7 +2054,7 @@ public: } tl::Variant o; - c->execute (m_context, o, v.get (), m_method, vv); + c->execute (m_context, o, v.get (), m_method, vv, kwargs.empty () ? 0 : &kwargs); v.swap (o); } @@ -2188,16 +2194,26 @@ public: void execute (EvalTarget &v) const { std::vector vv; + std::map kwargs; + vv.reserve (m_c.size ()); for (std::vector::const_iterator c = m_c.begin (); c != m_c.end (); ++c) { EvalTarget a; (*c)->execute (a); - vv.push_back (*a); + if ((*c)->name ().empty ()) { + vv.push_back (*a); + } else { + kwargs[(*c)->name ()] = *a; + } + } + + if (! kwargs.empty () && ! mp_func->supports_keyword_parameters ()) { + throw EvalError (tl::to_string (tr ("Keyword parameters not permitted")), m_context); } tl::Variant o; - mp_func->execute (m_context, o, vv); + mp_func->execute (m_context, o, vv, kwargs.empty () ? 0 : &kwargs); v.swap (o); } @@ -2939,7 +2955,7 @@ public: ms_functions.erase (m_name); } - void execute (const ExpressionParserContext &context, tl::Variant &out, const std::vector &args) const + void execute (const ExpressionParserContext &context, tl::Variant &out, const std::vector &args, const std::map * /*kwargs*/) const { m_func (context, out, args); } @@ -3556,8 +3572,19 @@ Eval::eval_suffix (ExpressionParserContext &ex, std::unique_ptr do { + tl::Extractor exn = ex; + std::string n; + if (exn.try_read_word (n, "_") && exn.test ("=")) { + // keyword parameter -> read name again to skip it + ex.read_word (n, "_"); + ex.expect ("="); + } else { + n.clear (); + } + std::unique_ptr a; eval_assign (ex, a); + a->set_name (n); m->add_child (a.release ()); if (ex.test (")")) { diff --git a/src/tl/tl/tlExpression.h b/src/tl/tl/tlExpression.h index 24d93403e..681e12498 100644 --- a/src/tl/tl/tlExpression.h +++ b/src/tl/tl/tlExpression.h @@ -186,7 +186,25 @@ public: /** * @brief Add a child node */ - void add_child (ExpressionNode *node); + void add_child (ExpressionNode *node); + + /** + * @brief Gets the name + * + * The name is used for named arguments for example. + */ + const std::string &name () const + { + return m_name; + } + + /** + * @brief Sets the name + */ + void set_name (const std::string &name) + { + m_name = name; + } /** * @brief Execute the node @@ -201,6 +219,7 @@ public: protected: std::vector m_c; ExpressionParserContext m_context; + std::string m_name; /** * @brief Sets the expression parent @@ -244,7 +263,7 @@ public: * * If no method of this kind exists, the implementation may throw a NoMethodError. */ - virtual void execute (const ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const = 0; + virtual void execute (const ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs) const = 0; }; /** @@ -263,6 +282,11 @@ public: */ virtual ~EvalFunction () { } + /** + * @brief Specifies whether keyword parameters are supported + */ + virtual bool supports_keyword_parameters () const { return false; } + /** * @brief The actual execution method * @@ -270,7 +294,7 @@ public: * @param args The arguments of the method * @return The return value */ - virtual void execute (const ExpressionParserContext &context, tl::Variant &out, const std::vector &args) const = 0; + virtual void execute (const ExpressionParserContext &context, tl::Variant &out, const std::vector &args, const std::map *kwargs) const = 0; }; /** diff --git a/src/tl/unit_tests/tlExpressionTests.cc b/src/tl/unit_tests/tlExpressionTests.cc index 834a72944..6ccda8598 100644 --- a/src/tl/unit_tests/tlExpressionTests.cc +++ b/src/tl/unit_tests/tlExpressionTests.cc @@ -445,7 +445,7 @@ private: class BoxClassClass : public tl::VariantUserClassBase, private tl::EvalClass { public: - virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const; + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs) const; virtual void *create () const { tl_assert (false); } virtual void destroy (void *) const { tl_assert (false); } @@ -473,7 +473,7 @@ BoxClassClass BoxClassClass::instance; class BoxClass : public tl::VariantUserClassImpl, private tl::EvalClass { public: - virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const; + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs) const; virtual const tl::EvalClass *eval_cls () const { return this; } static BoxClass instance; @@ -481,7 +481,7 @@ public: BoxClass BoxClass::instance; -void BoxClass::execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const +void BoxClass::execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map * /*kwargs*/) const { if (method == "width") { out = object.to_user ().width (); @@ -501,7 +501,7 @@ void BoxClass::execute (const tl::ExpressionParserContext &context, tl::Variant } } -void BoxClassClass::execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant & /*object*/, const std::string &method, const std::vector &args) const +void BoxClassClass::execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant & /*object*/, const std::string &method, const std::vector &args, const std::map * /*kwargs*/) const { if (method == "new") { out = tl::Variant (new Box (args[0].to_long(), args[1].to_long(), args[2].to_long(), args[3].to_long()), &BoxClass::instance, true); @@ -513,7 +513,7 @@ void BoxClassClass::execute (const tl::ExpressionParserContext &context, tl::Var class EdgeClassClass : public tl::VariantUserClassBase, private tl::EvalClass { public: - virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const; + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map *kwargs) const; virtual void *create () const { tl_assert (false); } virtual void destroy (void *) const { tl_assert (false); } @@ -541,7 +541,7 @@ EdgeClassClass EdgeClassClass::instance; class EdgeClass : public tl::VariantUserClassImpl, private tl::EvalClass { public: - virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args) const + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant &object, const std::string &method, const std::vector &args, const std::map * /*kwargs*/) const { if (method == "dx") { out = object.to_user ().dx (); @@ -566,7 +566,7 @@ public: EdgeClass EdgeClass::instance; void -EdgeClassClass::execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant & /*object*/, const std::string &method, const std::vector &args) const +EdgeClassClass::execute (const tl::ExpressionParserContext &context, tl::Variant &out, tl::Variant & /*object*/, const std::string &method, const std::vector &args, const std::map * /*kwargs*/) const { if (method == "new") { out = tl::Variant (new Edge (args[0].to_long(), args[1].to_long(), args[2].to_long(), args[3].to_long()), &EdgeClass::instance, true); @@ -996,7 +996,7 @@ class F0 : public tl::EvalFunction { public: - void execute (const tl::ExpressionParserContext &, tl::Variant &out, const std::vector &) const + void execute (const tl::ExpressionParserContext &, tl::Variant &out, const std::vector &, const std::map *) const { out = tl::Variant (17); } @@ -1006,7 +1006,7 @@ class F1 : public tl::EvalFunction { public: - void execute (const tl::ExpressionParserContext &, tl::Variant &out, const std::vector &vv) const + void execute (const tl::ExpressionParserContext &, tl::Variant &out, const std::vector &vv, const std::map *) const { out = tl::Variant (vv[0].to_long() + 1); } @@ -1016,7 +1016,7 @@ class F2 : public tl::EvalFunction { public: - void execute (const tl::ExpressionParserContext &, tl::Variant &out, const std::vector &vv) const + void execute (const tl::ExpressionParserContext &, tl::Variant &out, const std::vector &vv, const std::map *) const { out = tl::Variant (vv[0].to_long() + 2); } @@ -1026,7 +1026,7 @@ class F3 : public tl::EvalFunction { public: - void execute (const tl::ExpressionParserContext &, tl::Variant &out, const std::vector &vv) const + void execute (const tl::ExpressionParserContext &, tl::Variant &out, const std::vector &vv, const std::map *) const { out = tl::Variant (vv[0].to_long() + 3); }