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
{
#include <ostream>
#include <type_traits>
#include <algorithm>
+#include <limits>
class EvaluationFailure : public std::exception
{
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);
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));
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);
}
};
class EvaluationValueFunction
{
public:
- using Type = std::function<EvaluationValue_(const std::vector<EvaluationValue_>&)>;
class Arg
{
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
{
{
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,
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);
}
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);
};
}
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";
}
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();
{
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;
{
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();
}
}
-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
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>;
{
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;
};
* 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;
}
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;
}
}
+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 {