Adds support for keyword arguments 02/168302/10
authorRadoslaw Cybulski <r.cybulski@partner.samsung.com>
Thu, 25 Jan 2018 11:45:10 +0000 (12:45 +0100)
committerRadoslaw Cybulski <r.cybulski@partner.samsung.com>
Thu, 25 Jan 2018 16:36:06 +0000 (17:36 +0100)
Change-Id: I3f8a683fa89de7d414488f24fbb64348b78b0f4a

src/batch/EvaluationValue.cpp
src/batch/EvaluationValue.hpp
src/batch/EvaluationValueBase.cpp
src/batch/EvaluationValueBase.hpp
src/batch/EvaluationValueFunction.cpp
src/batch/EvaluationValueWait.cpp
src/batch/Evaluator.cpp
src/batch/Evaluator.hpp
src/batch/Parser.cpp
tests/no-ui-scenarios/BatchExecTests.cpp

index e5f7330..3e4a862 100644 (file)
@@ -141,9 +141,9 @@ EvaluationValue_::operator bool() const
        return value->oper_is_true();
 }
 
-EvaluationValue_ EvaluationValue_::operator()(const std::vector<EvaluationValue_> &values)
+EvaluationValue_ EvaluationValue_::operator()(const std::vector<EvaluationValue_> &values, const std::unordered_map<std::string, EvaluationValue_> &keyArgs)
 {
-       return value->oper_call(values);
+       return value->oper_call(values, keyArgs);
 }
 EvaluationValue_ EvaluationValue_::index_get(const EvaluationValue_ &i1) const
 {
index d62e6ea..efd7f99 100644 (file)
@@ -34,6 +34,7 @@
 #include <ostream>
 #include <type_traits>
 #include <algorithm>
+#include <limits>
 
 class EvaluationFailure : public std::exception
 {
@@ -208,7 +209,7 @@ public:
        bool contains(const EvaluationValue_ &) const;
        explicit operator bool() const;
 
-       EvaluationValue_ operator()(const std::vector<EvaluationValue_> &values);
+       EvaluationValue_ operator()(const std::vector<EvaluationValue_> &values, const std::unordered_map<std::string, EvaluationValue_> &keyArgs);
        EvaluationValue_ index_get(const EvaluationValue_ &) const;
        EvaluationValue_ index_get(const EvaluationValue_ &, const EvaluationValue_ &) const;
        EvaluationValue_ index_set(const EvaluationValue_ &, const EvaluationValue_ &value);
@@ -280,11 +281,14 @@ namespace detail
                return ah.apply_1();
        }
        template <size_t I, size_t S> struct Converter {
-               template <typename ... ARGS> static void convert(std::tuple<ARGS...> &dst,
-                               const std::vector<EvaluationValue_> &sourceArgs,
+               template <typename SARGS, typename ... ARGS> static void convert(std::tuple<ARGS...> &dst,
+                               const SARGS &sourceArgs,
                                size_t firstDefaultArgIndex,
-                               const std::vector<EvaluationValue_> &defaultArguments)
+                               const std::vector<EvaluationValue_> &defaultArguments,
+                               size_t consumeOnlyArguments = std::numeric_limits<size_t>::max())
                {
+                       DEBUG("%d %d", (int)I, (int)consumeOnlyArguments);
+                       if (I >= consumeOnlyArguments) return;
                        try {
                                auto &s = I < sourceArgs.size() ? sourceArgs[I] : defaultArguments[I - firstDefaultArgIndex];
                                s.convertTo(std::get<I>(dst));
@@ -292,13 +296,35 @@ namespace detail
                                e << ", when converting " << (I + 1) << " argument";
                                throw;
                        }
-                       Converter < I + 1, S >::convert(dst, sourceArgs, firstDefaultArgIndex, defaultArguments);
+                       Converter < I + 1, S >::convert(dst, sourceArgs, firstDefaultArgIndex, defaultArguments, consumeOnlyArguments);
+               }
+               template <typename ... ARGS> static void convertSingleArgumentByIndex(std::tuple<ARGS...> &dst,
+                               const EvaluationValue_ &arg,
+                               size_t index)
+               {
+                       if (I == index) {
+                               try {
+                                       arg.convertTo(std::get<I>(dst));
+                               } catch (EvaluationFailure &e) {
+                                       e << ", when converting " << (I + 1) << " argument";
+                                       throw;
+                               }
+                       }
+                       else {
+                               Converter < I + 1, S >::convertSingleArgumentByIndex(dst, arg, index);
+                       }
                }
        };
        template <size_t S> struct Converter<S, S> {
-               template <typename ... ARGS> static void convert(std::tuple<ARGS...> &, const std::vector<EvaluationValue_> &,
-                               size_t, const std::vector<EvaluationValue_> &)
+               template <typename SARGS, typename ... ARGS> static void convert(std::tuple<ARGS...> &, const SARGS &,
+                               size_t, const std::vector<EvaluationValue_> &, size_t = std::numeric_limits<size_t>::max())
+               {
+               }
+               template <typename ... ARGS> static void convertSingleArgumentByIndex(std::tuple<ARGS...> &,
+                               const EvaluationValue_ &,
+                               size_t)
                {
+                       ASSERT(0);
                }
        };
 
@@ -414,7 +440,6 @@ namespace detail
 class EvaluationValueFunction
 {
 public:
-       using Type = std::function<EvaluationValue_(const std::vector<EvaluationValue_>&)>;
        class Arg
        {
        public:
@@ -449,7 +474,8 @@ public:
        class Args
        {
        public:
-               Args(const std::vector<EvaluationValue_> &args) : args(args) { }
+               Args(const std::vector<EvaluationValue_> &args, 
+                       const std::unordered_map<std::string, EvaluationValue_> &keyArgs = {}) : args(args), keyArgs(keyArgs) { }
 
                bool empty() const
                {
@@ -463,9 +489,13 @@ public:
                {
                        return args[index];
                }
+               const std::unordered_map<std::string, EvaluationValue_> &keywords() const { return keyArgs; }
+               bool hasKeywords() const { return !keyArgs.empty(); }
        private:
                const std::vector<EvaluationValue_> &args;
+               const std::unordered_map<std::string, EvaluationValue_> &keyArgs;
        };
+       using Type = std::function<EvaluationValue_(const Args&)>;
        
        EvaluationValueFunction() = default;
        template <typename T, typename ARGS_TUPLE = typename detail::get_args_as_tuple_from_lambda<T>::type,
@@ -474,17 +504,14 @@ public:
                function(constructConvertingArgsFunction<ARGS_TUPLE>(std::move(f), std::move(argDescriptions))) { }
 
        template <typename T, typename = typename std::enable_if<std::is_constructible<std::function<EvaluationValue_(Args)>, T>::value>::type>
-       EvaluationValueFunction(T && f) : function([f = std::forward<T>(f)](const std::vector<EvaluationValue_> & args)
-       {
-               return f(Args{ args });
-       }) {}
+       EvaluationValueFunction(T && f) : function(std::move(f)) { }
 
        explicit operator bool () const
        {
                return bool(function);
        }
 
-       EvaluationValue_ operator()(const std::vector<EvaluationValue_> &args) const
+       EvaluationValue_ operator()(const Args &args) const
        {
                return function(args);
        }
@@ -514,11 +541,69 @@ private:
                std::reverse(defaultArguments.begin(), defaultArguments.end());
                auto firstDefaultArgIndex = expectedArgCount - defaultArguments.size();
 
-               return [f = std::move(f), defaultArguments = std::move(defaultArguments), firstDefaultArgIndex]
-               (const std::vector<EvaluationValue_> &sourceArgs) -> EvaluationValue_ {
-                       validateArgumentCount(sourceArgs.size(), firstDefaultArgIndex, expectedArgCount);
+               std::unordered_map<std::string, size_t> keywordIndexes;
+               size_t argIndex = 0;
+               for(auto &a : argDescriptions) {        
+                       if (!a.getName().empty()) {
+                               auto it = keywordIndexes.insert({ a.getName(), argIndex });
+                               ASSERT(it.second); // every arg name must be unique
+                       }
+                       ++argIndex;
+               }
+               return [f = std::move(f), defaultArguments = std::move(defaultArguments), firstDefaultArgIndex, keywordIndexes = std::move(keywordIndexes)]
+               (const Args &sourceArgs) -> EvaluationValue_ {
                        ARGS_TUPLE args;
-                       detail::Converter<0, expectedArgCount>::convert(args, sourceArgs, firstDefaultArgIndex, defaultArguments);
+                       if (!sourceArgs.hasKeywords()) {
+                               DEBUG(".");
+                               validateArgumentCount(sourceArgs.size(), firstDefaultArgIndex, expectedArgCount);
+                               DEBUG(".");
+                               detail::Converter<0, expectedArgCount>::convert(args, sourceArgs, firstDefaultArgIndex, defaultArguments);
+                               DEBUG(".");
+                       }
+                       else {
+                               DEBUG(".");
+                               std::vector<bool> used(expectedArgCount, false);
+                               DEBUG("%d", (int)sourceArgs.size());
+                               detail::Converter<0, expectedArgCount>::convert(args, sourceArgs, firstDefaultArgIndex, defaultArguments, sourceArgs.size());
+                               DEBUG(".");
+                               for(auto i = 0u; i < sourceArgs.size(); ++i) used[i] = true;
+                               DEBUG(".");
+                               for(auto &val : sourceArgs.keywords()) {
+                                       DEBUG(".");
+                                       auto it = keywordIndexes.find(val.first);
+                                       if (it == keywordIndexes.end()) {
+                                               DEBUG(".");
+                                               throw EvaluationFailure{} << "unknown keyword argument '" << val.first << "'";
+                                       }
+                                       DEBUG("%d", (int)it->second);
+                                       if (used[it->second]) {
+                                               DEBUG(".");
+                                               throw EvaluationFailure{} << "argument '" << val.first << "' is already set (when applying keyword)";
+                                       }
+                                       DEBUG(". %d", (int)it->second);
+                                       used[it->second] = true;
+                                       DEBUG(".");
+                                       detail::Converter<0, expectedArgCount>::convertSingleArgumentByIndex(args, val.second, it->second);
+                                       DEBUG(".");
+                               }
+                               DEBUG("%d %d", (int)firstDefaultArgIndex, (int)expectedArgCount);
+                               for(auto i = 0u; i < firstDefaultArgIndex; ++i) {
+                                       if (!used[i]) {
+                                               DEBUG(".");
+                                               throw EvaluationFailure{} << "argument " << (i + 1) << " not set";
+                                       }
+                               }
+                               DEBUG(".");
+                               for(auto i = firstDefaultArgIndex; i < expectedArgCount; ++i) {
+                                       DEBUG(".");
+                                       if (!used[i]) {
+                                               DEBUG(".");
+                                               auto &val = defaultArguments[i - firstDefaultArgIndex];
+                                               detail::Converter<0, expectedArgCount>::convertSingleArgumentByIndex(args, val, i);
+                                       }
+                               }
+                               DEBUG(".");
+                       }
                        return detail::apply(f, args);
                };
        }
index ee71123..60027e1 100644 (file)
@@ -115,7 +115,7 @@ bool EvaluationValueBase::oper_contains(const EvaluationValuePtr &value)
        throw EvaluationFailure{} << "unsupported operation, " << typeName() << " is not a container";
 }
 
-EvaluationValuePtr EvaluationValueBase::oper_call(const std::vector<EvaluationValue_> &args)
+EvaluationValuePtr EvaluationValueBase::oper_call(const std::vector<EvaluationValue_> &, const std::unordered_map<std::string, EvaluationValue_> &)
 {
        throw EvaluationFailure{} << "unsupported operation, " << typeName() << " is not a callable";
 }
index cb7ac23..a13bc02 100644 (file)
@@ -65,7 +65,7 @@ public:
        virtual size_t oper_hash();
        virtual bool oper_contains(const EvaluationValuePtr &value);
        virtual bool oper_is_true() = 0;
-       virtual EvaluationValuePtr oper_call(const std::vector<EvaluationValue_> &args);
+       virtual EvaluationValuePtr oper_call(const std::vector<EvaluationValue_> &args, const std::unordered_map<std::string, EvaluationValue_> &keyArgs);
        virtual EvaluationValuePtr oper_attr_get(const std::string &name);
        virtual EvaluationValuePtr oper_attr_set(const std::string &name, const EvaluationValuePtr &);
        virtual EvaluationValuePtr oper_negate();
index 983952a..8d28552 100644 (file)
@@ -43,9 +43,9 @@ public:
        {
                return true;
        }
-       EvaluationValuePtr oper_call(const std::vector<EvaluationValue_> &args) override
+       EvaluationValuePtr oper_call(const std::vector<EvaluationValue_> &args, const std::unordered_map<std::string, EvaluationValue_> &keyArgs) override
        {
-               return value(args).getInternalValue();
+               return value(EvaluationValueFunction::Args{ args, keyArgs }).getInternalValue();
        }
 private:
        EvaluationValueFunction value;
index 3ee80c3..dbd1010 100644 (file)
@@ -30,9 +30,10 @@ public:
        {
                return other->isBoolean() && value == other->asWait();
        }
-       EvaluationValuePtr oper_call(const std::vector<EvaluationValue_> &args) override
+       EvaluationValuePtr oper_call(const std::vector<EvaluationValue_> &args, const std::unordered_map<std::string, EvaluationValue_> &keyArgs) override
        {
                EvaluationValueFunction::validateArgumentCount(args.size(), 0, 0);
+               if (!keyArgs.empty()) throw EvaluationFailure{} << "wait object doesn't accept keyword arguments, when called";
                value->join();
                return create();
        }
index d8d73da..dbd8141 100644 (file)
@@ -442,17 +442,21 @@ void BooleanEvaluator::printSelfInfo(std::ostringstream &os, unsigned int depth)
 }
 
 
-CallEvaluator::CallEvaluator(TokenLocation tokenLocation, ExprPtr function, ExprPtrs args) :
-       ExpressionEvaluator(tokenLocation), function(std::move(function)), args(std::move(args)) { }
+CallEvaluator::CallEvaluator(TokenLocation tokenLocation, ExprPtr function, ExprPtrs args, ExprMapPtrs keywordArgs) :
+       ExpressionEvaluator(tokenLocation), function(std::move(function)), args(std::move(args)), keywordArgs(std::move(keywordArgs)) { }
 
 EvaluationValue_ CallEvaluator::evaluateImpl() const
 {
        std::vector<EvaluationValue_> values;
+       std::unordered_map<std::string, EvaluationValue_> keywordValues;
        values.reserve(args.size());
        for (auto &arg : args) {
                values.push_back(arg->evaluate());
        }
-       return function->evaluate()(values);
+       for(auto &it : keywordArgs) {
+               keywordValues[it.first] = it.second->evaluate();
+       }
+       return function->evaluate()(values, keywordValues);
 }
 
 void CallEvaluator::printSelfInfo(std::ostringstream &os, unsigned int depth) const
index a8973e1..3774d85 100644 (file)
@@ -150,6 +150,7 @@ protected:
 
 using ExprPtr = std::shared_ptr<ExpressionEvaluator>;
 using ExprPtrs = std::vector<ExprPtr>;
+using ExprMapPtrs = std::unordered_map<std::string, ExprPtr>;
 using StatPtr = std::shared_ptr<StatementEvaluator>;
 using StatPtrs = std::vector<StatPtr>;
 
@@ -337,10 +338,11 @@ class CallEvaluator : public ExpressionEvaluator
 {
        ExprPtr function;
        ExprPtrs args;
+       ExprMapPtrs keywordArgs;
 protected:
        EvaluationValue_ evaluateImpl() const override;
 public:
-       CallEvaluator(TokenLocation location_, ExprPtr function, ExprPtrs args);
+       CallEvaluator(TokenLocation location_, ExprPtr function, ExprPtrs args, ExprMapPtrs keywordArgs);
 
        void printSelfInfo(std::ostringstream &os, unsigned int depth) const override;
 };
index 1a9a7c3..038a7d8 100644 (file)
@@ -198,16 +198,38 @@ class Parser
         * Returns true, if successful. Note, that this helper function doesn't consume opening parenthesis
         * (, not closing one - caller is responsible for parsing and consuming those.
         */
-       bool parseArguments(ExprPtrs &args)
+       bool parseArguments(ExprPtrs &args, std::unordered_map<std::string, ExprPtr> &keywordArgs)
        {
                args.clear();
+               keywordArgs.clear();
+               bool alreadyKeywords = false;
 
                while (index < tokens.size()) {
                        if (canConsume(")")) break;
                        if (!args.empty() && !consume(",")) return false;
-                       auto e = parseExpr();
-                       if (!e) return false;
-                       args.push_back(std::move(e));
+                       if (index + 1 < tokens.size() && tokens[index].type() == TokenType::IDENTIFIER &&
+                                       tokens[index + 1].text() == "=") {
+                               alreadyKeywords = true;
+                               const auto &identifier = tokens[index].text();
+                               auto it = keywordArgs.insert({ identifier, nullptr });
+                               if (!it.second) {
+                                       error("keyword argument '" + identifier + "' already used");
+                                       return false;
+                               }
+                               index += 2;
+                               auto e = parseExpr();
+                               if (!e) return false;
+                               it.first->second = std::move(e);                                
+                       }
+                       else {
+                               if (alreadyKeywords) {
+                                       error("positional argument after keyword argument is not allowed");
+                                       return false;
+                               }
+                               auto e = parseExpr();
+                               if (!e) return false;
+                               args.push_back(std::move(e));
+                       }
                }
                return true;
        }
@@ -372,10 +394,11 @@ class Parser
                        if (tokens[index].text() == "(") {
                                auto location = getLocation();
                                ExprPtrs args;
+                               std::unordered_map<std::string, ExprPtr> keywordArgs;
                                ++index;
-                               if (!parseArguments(args)) return {};
+                               if (!parseArguments(args, keywordArgs)) return {};
                                if (!consume(")")) return {};
-                               e = std::make_shared<CallEvaluator>(std::move(location), std::move(e), std::move(args));
+                               e = std::make_shared<CallEvaluator>(std::move(location), std::move(e), std::move(args), std::move(keywordArgs));
                        } else if (tokens[index].text() == ".") {
                                auto location = getLocation();
                                ++index;
index 434d9dc..fd0bf6c 100644 (file)
@@ -414,6 +414,63 @@ TEST_F(ScriptTest, defaultArguments)
        }
 }
 
+TEST_F(ScriptTest, keywordArguments)
+{
+       std::tuple<int, int, int, int> val, expected;
+
+       vars["func"] = EvaluationValueFunction{
+               [&](int a1, int a2, int a3, int a4) -> EvaluationValue_ {
+                       std::get<0>(val) = a1;
+                       std::get<1>(val) = a2;
+                       std::get<2>(val) = a3;
+                       std::get<3>(val) = a4;
+                       return {};
+               }, {
+                       { "a1" },
+                       { "a2" },
+                       { "a3", 1 },
+                       { "a4", 2 }
+               } };
+
+       TestExecutor exec{ std::move(vars) };
+       EvaluationContext ec{ exec };
+
+       DEBUG(".");
+       execute("func(6,7,8,a4=9)", ec, true);
+       expected = { 6, 7, 8, 9};
+       ASSERT_EQ(val, expected);
+
+       DEBUG(".");
+       execute("func(5, 6, a3=3)", ec, true);
+       expected = { 5, 6, 3, 2};
+       ASSERT_EQ(val, expected);
+
+       DEBUG(".");
+       execute("func(5, 6, a4=3)", ec, true);
+       expected = { 5, 6, 1, 3};
+       ASSERT_EQ(val, expected);
+
+       DEBUG(".");
+       execute("func(5, 6, a4=3, a3=5)", ec, true);
+       expected = { 5, 6, 5, 3};
+       ASSERT_EQ(val, expected);
+
+       DEBUG(".");
+       try {
+               execute("func(1,a3=5,a4=6)", ec, true);
+               FAIL() << "didn't throw";
+       } catch (EvaluationFailure &) {
+       }
+
+       DEBUG(".");
+       try {
+               execute("func(1,2,3,4,a3=5)", ec, true);
+               FAIL() << "didn't throw";
+       } catch (EvaluationFailure &) {
+       }
+       DEBUG(".");
+}
+
 int main(int argc, char *argv[])
 {
        try {