#include "testers.h"
#include "value_visitors.h"
namespace jinja2
{
template<typename F>
struct TesterFactory
{
static TesterPtr Create(TesterParams params)
{
return std::make_shared<F>(std::move(params));
}
template<typename ... Args>
static IsExpression::TesterFactoryFn MakeCreator(Args&& ... args)
{
return [args...](TesterParams params) {return std::make_shared<F>(std::move(params), args...);};
}
};
std::unordered_map<std::string, IsExpression::TesterFactoryFn> s_testers = {
{"defined", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsDefinedMode)},
{"startsWith", &TesterFactory<testers::StartsWith>::Create},
{"eq", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalEq)},
{"==", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalEq)},
{"equalto", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalEq)},
{"even", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsEvenMode)},
{"ge", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGe)},
{">=", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGe)},
{"gt", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGt)},
{">", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGt)},
{"greaterthan", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGt)},
{"in", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsInMode)},
{"iterable", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsIterableMode)},
{"le", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLe)},
{"<=", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLe)},
{"lower", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsLowerMode)},
{"lt", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLt)},
{"<", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLt)},
{"lessthan", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLt)},
{"mapping", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsMappingMode)},
{"ne", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalNe)},
{"!=", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalNe)},
{"number", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsNumberMode)},
{"odd", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsOddMode)},
{"sequence", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsSequenceMode)},
{"string", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsStringMode)},
{"undefined", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsUndefinedMode)},
{"upper", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsUpperMode)},
};
TesterPtr CreateTester(std::string testerName, CallParamsInfo params)
{
auto p = s_testers.find(testerName);
if (p == s_testers.end())
return std::make_shared<testers::UserDefinedTester>(std::move(testerName), std::move(params));
return p->second(std::move(params));
}
namespace testers
{
Comparator::Comparator(TesterParams params, BinaryExpression::Operation op)
: m_op(op)
{
ParseParams({{"b", true}}, params);
}
bool Comparator::Test(const InternalValue& baseVal, RenderContext& context)
{
auto b = GetArgumentValue("b", context);
auto cmpRes = Apply2<visitors::BinaryMathOperation>(baseVal, b, m_op);
return ConvertToBool(cmpRes);
}
#if 0
bool Defined::Test(const InternalValue& baseVal, RenderContext& /*context*/)
{
return boost::get<EmptyValue>(&baseVal) == nullptr;
}
#endif
StartsWith::StartsWith(TesterParams params)
{
bool parsed = true;
auto args = helpers::ParseCallParamsInfo({ { "str", true } }, params, parsed);
m_stringEval = args["str"];
}
bool StartsWith::Test(const InternalValue& baseVal, RenderContext& context)
{
InternalValue val = m_stringEval->Evaluate(context);
std::string baseStr = AsString(baseVal);
std::string str = AsString(val);
return baseStr.find(str) == 0;
}
ValueTester::ValueTester(TesterParams params, ValueTester::Mode mode)
: m_mode(mode)
{
switch (m_mode)
{
case IsDefinedMode:
break;
case IsEvenMode:
break;
case IsInMode:
ParseParams({{"seq", true}}, params);
break;
case IsIterableMode:
break;
case IsLowerMode:
break;
case IsMappingMode:
break;
case IsNumberMode:
break;
case IsOddMode:
break;
case IsSequenceMode:
break;
case IsStringMode:
break;
case IsUndefinedMode:
break;
case IsUpperMode:
break;
}
}
enum class ValueKind
{
Empty,
Boolean,
String,
Integer,
Double,
List,
Map,
KVPair,
Callable,
Renderer
};
struct ValueKindGetter : visitors::BaseVisitor<ValueKind>
{
using visitors::BaseVisitor<ValueKind>::operator ();
ValueKind operator()(const EmptyValue&) const
{
return ValueKind::Empty;
}
ValueKind operator()(bool) const
{
return ValueKind::Boolean;
}
template<typename CharT>
ValueKind operator()(const std::basic_string<CharT>&) const
{
return ValueKind::String;
}
template<typename CharT>
ValueKind operator()(const std::basic_string_view<CharT>&) const
{
return ValueKind::String;
}
ValueKind operator()(int64_t) const
{
return ValueKind::Integer;
}
ValueKind operator()(double) const
{
return ValueKind::Double;
}
ValueKind operator()(const ListAdapter&) const
{
return ValueKind::List;
}
ValueKind operator()(const MapAdapter&) const
{
return ValueKind::Map;
}
ValueKind operator()(const KeyValuePair&) const
{
return ValueKind::KVPair;
}
ValueKind operator()(const Callable&) const
{
return ValueKind::Callable;
}
ValueKind operator()(IRendererBase*) const
{
return ValueKind::Renderer;
}
};
bool ValueTester::Test(const InternalValue& baseVal, RenderContext& context)
{
bool result = false;
auto valKind = Apply<ValueKindGetter>(baseVal);
enum
{
EvenTest,
OddTest
};
int testMode = EvenTest;
auto evenOddTest = [&testMode, valKind](const InternalValue& val) -> bool
{
bool result = false;
if (valKind == ValueKind::Integer)
{
auto intVal = ConvertToInt(val);
result = (intVal & 1) == (testMode == EvenTest ? 0 : 1);
}
else if (valKind == ValueKind::Double)
{
auto dblVal = ConvertToDouble(val);
int64_t intVal = static_cast<int64_t>(dblVal);
if (dblVal == intVal)
result = (intVal & 1) == (testMode == EvenTest ? 0 : 1);
}
return result;
};
switch (m_mode)
{
case IsIterableMode:
result = valKind == ValueKind::List || valKind == ValueKind::Map;
break;
case IsMappingMode:
result = valKind == ValueKind::KVPair || valKind == ValueKind::Map;
break;
case IsNumberMode:
result = valKind == ValueKind::Integer || valKind == ValueKind::Double;
break;
case IsSequenceMode:
result = valKind == ValueKind::List;
break;
case IsStringMode:
result = valKind == ValueKind::String;
break;
case IsDefinedMode:
result = valKind != ValueKind::Empty;
break;
case IsUndefinedMode:
result = valKind == ValueKind::Empty;
break;
case IsInMode:
{
bool isConverted = false;
auto seq = GetArgumentValue("seq", context);
auto seqKind = Apply<ValueKindGetter>(seq);
if (seqKind == ValueKind::List) {
ListAdapter values = ConvertToList(seq, InternalValue(), isConverted);
if (!isConverted)
return false;
auto equalComparator = [&baseVal](auto& val) {
InternalValue cmpRes;
cmpRes = Apply2<visitors::BinaryMathOperation>(val, baseVal, BinaryExpression::LogicalEq);
return ConvertToBool(cmpRes);
};
auto p = std::find_if(values.begin(), values.end(), equalComparator);
result = p != values.end();
} else if (seqKind == ValueKind::String) {
result = ApplyStringConverter(baseVal, [&](const auto& srcStr) {
std::decay_t<decltype(srcStr)> emptyStrView;
using CharT = typename decltype(emptyStrView)::value_type;
std::basic_string<CharT> emptyStr;
auto substring = sv_to_string(srcStr);
auto seq = GetAsSameString(srcStr, this->GetArgumentValue("seq", context)).value_or(emptyStr);
return seq.find(substring) != std::string::npos;
});
}
break;
}
case IsEvenMode:
{
testMode = EvenTest;
result = evenOddTest(baseVal);
break;
}
case IsOddMode:
{
testMode = OddTest;
result = evenOddTest(baseVal);
break;
}
case IsLowerMode:
if (valKind != ValueKind::String)
{
result = false;
}
else
{
result = ApplyStringConverter(baseVal, [](const auto& str) {
bool result = true;
for (auto& ch : str)
{
if (std::isalpha(ch, std::locale()) && std::isupper(ch, std::locale()))
{
result = false;
break;
}
}
return result;
});
}
break;
case IsUpperMode:
if (valKind != ValueKind::String)
{
result = false;
}
else
{
result = ApplyStringConverter(baseVal, [](const auto& str) {
bool result = true;
for (auto& ch : str)
{
if (std::isalpha(ch, std::locale()) && std::islower(ch, std::locale()))
{
result = false;
break;
}
}
return result;
});
}
break;
}
return result;
}
UserDefinedTester::UserDefinedTester(std::string testerName, TesterParams params)
: m_testerName(std::move(testerName))
{
ParseParams({{"*args"}, {"**kwargs"}}, params);
m_callParams.kwParams = m_args.extraKwArgs;
m_callParams.posParams = m_args.extraPosArgs;
}
bool UserDefinedTester::Test(const InternalValue& baseVal, RenderContext& context)
{
bool testerFound = false;
auto testerValPtr = context.FindValue(m_testerName, testerFound);
if (!testerFound)
return false;
const Callable* callable = GetIf<Callable>(&testerValPtr->second);
if (callable == nullptr || callable->GetKind() != Callable::UserCallable)
return false;
CallParams tmpCallParams = helpers::EvaluateCallParams(m_callParams, context);
CallParams callParams;
callParams.kwParams = std::move(tmpCallParams.kwParams);
callParams.posParams.reserve(tmpCallParams.posParams.size() + 1);
callParams.posParams.push_back(baseVal);
for (auto& p : tmpCallParams.posParams)
callParams.posParams.push_back(std::move(p));
InternalValue result;
if (callable->GetType() != Callable::Type::Expression)
return false;
return ConvertToBool(callable->GetExpressionCallable()(callParams, context));
}
} // namespace testers
} // namespace jinja2