#include "expression_evaluator.h"
#include "filters.h"
#include "generic_adapters.h"
#include "internal_value.h"
#include "out_stream.h"
#include "testers.h"
#include "value_visitors.h"
#include <boost/algorithm/string/join.hpp>
#include <boost/container/small_vector.hpp>
#include <cmath>
#include <stack>
namespace jinja2
{
void ExpressionEvaluatorBase::Render(OutStream& stream, RenderContext& values)
{
auto val = Evaluate(values);
stream.WriteValue(val);
}
InternalValue FullExpressionEvaluator::Evaluate(RenderContext& values)
{
if (!m_expression)
return InternalValue();
auto result = m_expression->Evaluate(values);
if (m_tester && !m_tester->Evaluate(values))
return m_tester->EvaluateAltValue(values);
return result;
}
void FullExpressionEvaluator::Render(OutStream& stream, RenderContext& values)
{
if (!m_tester)
m_expression->Render(stream, values);
else
Expression::Render(stream, values);
}
InternalValue ValueRefExpression::Evaluate(RenderContext& values)
{
bool found = false;
auto p = values.FindValue(m_valueName, found);
if (found)
return p->second;
return InternalValue();
}
InternalValue SubscriptExpression::Evaluate(RenderContext& values)
{
InternalValue cur = m_value->Evaluate(values);
for (auto idx : m_subscriptExprs)
{
auto subscript = idx->Evaluate(values);
auto newVal = Subscript(cur, subscript, &values);
if (cur.ShouldExtendLifetime())
newVal.SetParentData(cur);
std::swap(newVal, cur);
}
return cur;
}
InternalValue FilteredExpression::Evaluate(RenderContext& values)
{
auto origResult = m_expression->Evaluate(values);
return m_filter->Evaluate(origResult, values);
}
InternalValue UnaryExpression::Evaluate(RenderContext& values)
{
return Apply<visitors::UnaryOperation>(m_expr->Evaluate(values), m_oper);
}
BinaryExpression::BinaryExpression(BinaryExpression::Operation oper, ExpressionEvaluatorPtr<> leftExpr, ExpressionEvaluatorPtr<> rightExpr)
: m_oper(oper)
, m_leftExpr(leftExpr)
, m_rightExpr(rightExpr)
{
if (m_oper == In)
{
CallParamsInfo params;
params.kwParams["seq"] = rightExpr;
m_inTester = CreateTester("in", params);
}
}
InternalValue BinaryExpression::Evaluate(RenderContext& context)
{
InternalValue leftVal = m_leftExpr->Evaluate(context);
InternalValue rightVal = m_oper == In ? InternalValue() : m_rightExpr->Evaluate(context);
InternalValue result;
switch (m_oper)
{
case jinja2::BinaryExpression::LogicalAnd:
{
bool left = ConvertToBool(leftVal);
if (left)
left = ConvertToBool(rightVal);
result = static_cast<bool>(left);
break;
}
case jinja2::BinaryExpression::LogicalOr:
{
bool left = ConvertToBool(leftVal);
if (!left)
left = ConvertToBool(rightVal);
result = static_cast<bool>(left);
break;
}
case jinja2::BinaryExpression::LogicalEq:
case jinja2::BinaryExpression::LogicalNe:
case jinja2::BinaryExpression::LogicalGt:
case jinja2::BinaryExpression::LogicalLt:
case jinja2::BinaryExpression::LogicalGe:
case jinja2::BinaryExpression::LogicalLe:
case jinja2::BinaryExpression::Plus:
case jinja2::BinaryExpression::Minus:
case jinja2::BinaryExpression::Mul:
case jinja2::BinaryExpression::Div:
case jinja2::BinaryExpression::DivReminder:
case jinja2::BinaryExpression::DivInteger:
case jinja2::BinaryExpression::Pow:
result = Apply2<visitors::BinaryMathOperation>(leftVal, rightVal, m_oper);
break;
case jinja2::BinaryExpression::In:
{
result = m_inTester->Test(leftVal, context);
break;
}
case jinja2::BinaryExpression::StringConcat:
{
auto leftStr = context.GetRendererCallback()->GetAsTargetString(leftVal);
auto rightStr = context.GetRendererCallback()->GetAsTargetString(rightVal);
TargetString resultStr;
std::string* nleftStr = GetIf<std::string>(&leftStr);
if (nleftStr != nullptr)
{
auto* nrightStr = GetIf<std::string>(&rightStr);
resultStr = *nleftStr + *nrightStr;
}
else
{
auto* wleftStr = GetIf<std::wstring>(&leftStr);
auto* wrightStr = GetIf<std::wstring>(&rightStr);
resultStr = *wleftStr + *wrightStr;
}
result = InternalValue(std::move(resultStr));
break;
}
default:
break;
}
return result;
}
InternalValue TupleCreator::Evaluate(RenderContext& context)
{
InternalValueList result;
for (auto& e : m_exprs)
{
result.push_back(e->Evaluate(context));
}
return ListAdapter::CreateAdapter(std::move(result));
}
InternalValue DictCreator::Evaluate(RenderContext& context)
{
InternalValueMap result;
for (auto& e : m_exprs)
{
result[e.first] = e.second->Evaluate(context);
}
return CreateMapAdapter(std::move(result));;
}
ExpressionFilter::ExpressionFilter(const std::string& filterName, CallParamsInfo params)
{
m_filter = CreateFilter(filterName, std::move(params));
if (!m_filter)
throw std::runtime_error("Can't find filter '" + filterName + "'");
}
InternalValue ExpressionFilter::Evaluate(const InternalValue& baseVal, RenderContext& context)
{
if (m_parentFilter)
return m_filter->Filter(m_parentFilter->Evaluate(baseVal, context), context);
return m_filter->Filter(baseVal, context);
}
IsExpression::IsExpression(ExpressionEvaluatorPtr<> value, const std::string& tester, CallParamsInfo params)
: m_value(value)
{
m_tester = CreateTester(tester, std::move(params));
if (!m_tester)
throw std::runtime_error("Can't find tester '" + tester + "'");
}
InternalValue IsExpression::Evaluate(RenderContext& context)
{
return m_tester->Test(m_value->Evaluate(context), context);
}
bool IfExpression::Evaluate(RenderContext& context)
{
return ConvertToBool(m_testExpr->Evaluate(context));
}
InternalValue IfExpression::EvaluateAltValue(RenderContext& context)
{
return m_altValue ? m_altValue->Evaluate(context) : InternalValue();
}
/*
InternalValue DictionaryCreator::Evaluate(RenderContext& context)
{
ValuesMap result;
for (auto& i : m_items)
{
result[i.first] = i.second->Evaluate(context);
}
return result;
}*/
InternalValue CallExpression::Evaluate(RenderContext& values)
{
auto fn = m_valueRef->Evaluate(values);
auto fnId = ConvertToInt(fn, InvalidFn);
switch (fnId)
{
case RangeFn:
return CallGlobalRange(values);
case LoopCycleFn:
return CallLoopCycle(values);
default:
return CallArbitraryFn(values);
}
}
void CallExpression::Render(OutStream& stream, RenderContext& values)
{
auto fnVal = m_valueRef->Evaluate(values);
const Callable* callable = GetIf<Callable>(&fnVal);
if (callable == nullptr)
{
fnVal = Subscript(fnVal, std::string("operator()"), &values);
callable = GetIf<Callable>(&fnVal);
if (callable == nullptr)
{
Expression::Render(stream, values);
return;
}
}
auto callParams = helpers::EvaluateCallParams(m_params, values);
if (callable->GetType() == Callable::Type::Expression)
{
stream.WriteValue(callable->GetExpressionCallable()(callParams, values));
}
else
{
callable->GetStatementCallable()(callParams, stream, values);
}
}
InternalValue CallExpression::CallArbitraryFn(RenderContext& values)
{
auto fnVal = m_valueRef->Evaluate(values);
Callable* callable = GetIf<Callable>(&fnVal);
if (callable == nullptr)
{
fnVal = Subscript(fnVal, std::string("operator()"), nullptr);
callable = GetIf<Callable>(&fnVal);
if (callable == nullptr)
return InternalValue();
}
auto kind = callable->GetKind();
if (kind != Callable::GlobalFunc && kind != Callable::UserCallable && kind != Callable::Macro)
return InternalValue();
auto callParams = helpers::EvaluateCallParams(m_params, values);
if (callable->GetType() == Callable::Type::Expression)
{
return callable->GetExpressionCallable()(callParams, values);
}
TargetString resultStr;
auto stream = values.GetRendererCallback()->GetStreamOnString(resultStr);
callable->GetStatementCallable()(callParams, stream, values);
return resultStr;
}
InternalValue CallExpression::CallGlobalRange(RenderContext& values)
{
bool isArgsParsed = true;
auto args = helpers::ParseCallParamsInfo({ { "start" }, { "stop", true }, { "step" } }, m_params, isArgsParsed);
if (!isArgsParsed)
return InternalValue();
auto startExpr = args["start"];
auto stopExpr = args["stop"];
auto stepExpr = args["step"];
InternalValue startVal = startExpr ? startExpr->Evaluate(values) : InternalValue();
InternalValue stopVal = stopExpr ? stopExpr->Evaluate(values) : InternalValue();
InternalValue stepVal = stepExpr ? stepExpr->Evaluate(values) : InternalValue();
int64_t start = Apply<visitors::IntegerEvaluator>(startVal);
int64_t stop = Apply<visitors::IntegerEvaluator>(stopVal);
int64_t step = Apply<visitors::IntegerEvaluator>(stepVal);
if (!stepExpr)
{
step = 1;
}
else
{
if (step == 0)
return InternalValue();
}
auto distance = stop - start;
auto items_count = distance / step;
items_count = items_count < 0 ? 0 : static_cast<size_t>(items_count);
return ListAdapter::CreateAdapter(static_cast<size_t>(items_count),
[start, step](size_t idx) { return InternalValue(static_cast<int64_t>(start + step * idx)); });
}
InternalValue CallExpression::CallLoopCycle(RenderContext& values)
{
bool loopFound = false;
auto loopValP = values.FindValue("loop", loopFound);
if (!loopFound)
return InternalValue();
auto loop = GetIf<MapAdapter>(&loopValP->second);
int64_t baseIdx = Apply<visitors::IntegerEvaluator>(loop->GetValueByName("index0"));
auto idx = static_cast<size_t>(baseIdx % m_params.posParams.size());
return m_params.posParams[idx]->Evaluate(values);
}
void SetupGlobals(InternalValueMap& globalParams)
{
globalParams["range"] = InternalValue(static_cast<int64_t>(RangeFn));
// globalParams["loop"] = MapAdapter::CreateAdapter(InternalValueMap{{"cycle", InternalValue(static_cast<int64_t>(LoopCycleFn))}});
}
namespace helpers
{
enum ArgState
{
NotFound,
NotFoundMandatory,
Keyword,
Positional,
Ignored
};
enum ParamState
{
UnknownPos,
UnknownKw,
MappedPos,
MappedKw,
};
template<typename Result>
struct ParsedArgumentDefaultValGetter;
template<>
struct ParsedArgumentDefaultValGetter<ParsedArguments>
{
static auto Get(const InternalValue& val) { return val; }
};
template<>
struct ParsedArgumentDefaultValGetter<ParsedArgumentsInfo>
{
static auto Get(const InternalValue& val) { return std::make_shared<ConstantExpression>(val); }
};
template<typename Result, typename T, typename P>
Result ParseCallParamsImpl(const T& args, const P& params, bool& isSucceeded)
{
struct ArgInfo
{
ArgState state = NotFound;
int prevNotFound = -1;
int nextNotFound = -1;
const ArgumentInfo* info = nullptr;
};
boost::container::small_vector<ArgInfo, 8> argsInfo(args.size());
boost::container::small_vector<ParamState, 8> posParamsInfo(params.posParams.size());
isSucceeded = true;
Result result;
int argIdx = 0;
int firstMandatoryIdx = -1;
int prevNotFound = -1;
int foundKwArgs = 0;
(void)foundKwArgs; // extremely odd bug in clang warning
// Wunused-but-set-variable
// Find all provided keyword args
for (auto& argInfo : args)
{
argsInfo[argIdx].info = &argInfo;
if (argInfo.name == "*args" || argInfo.name=="**kwargs")
{
argsInfo[argIdx ++].state = Ignored;
continue;
}
auto p = params.kwParams.find(argInfo.name);
if (p != params.kwParams.end())
{
result.args[argInfo.name] = p->second;
argsInfo[argIdx].state = Keyword;
++foundKwArgs;
}
else
{
if (argInfo.mandatory)
{
argsInfo[argIdx].state = NotFoundMandatory;
if (firstMandatoryIdx == -1)
firstMandatoryIdx = argIdx;
}
else
{
argsInfo[argIdx].state = NotFound;
}
if (prevNotFound != -1)
argsInfo[prevNotFound].nextNotFound = argIdx;
argsInfo[argIdx].prevNotFound = prevNotFound;
prevNotFound = argIdx;
}
++ argIdx;
}
std::size_t startPosArg = firstMandatoryIdx == -1 ? 0 : firstMandatoryIdx;
std::size_t curPosArg = startPosArg;
std::size_t eatenPosArgs = 0;
// Determine the range for positional arguments scanning
bool isFirstTime = true;
for (; eatenPosArgs < posParamsInfo.size() && startPosArg < args.size(); eatenPosArgs = eatenPosArgs + (argsInfo[startPosArg].state == Ignored ? 0 : 1))
{
if (isFirstTime)
{
for (; startPosArg < args.size() && (argsInfo[startPosArg].state == Keyword || argsInfo[startPosArg].state == Positional); ++ startPosArg)
;
isFirstTime = false;
if (startPosArg == args.size())
break;
continue;
}
prevNotFound = argsInfo[startPosArg].prevNotFound;
if (prevNotFound != -1)
{
startPosArg = static_cast<std::size_t>(prevNotFound);
}
else if (curPosArg == args.size())
{
break;
}
else
{
int nextPosArg = argsInfo[curPosArg].nextNotFound;
if (nextPosArg == -1)
break;
curPosArg = static_cast<std::size_t>(nextPosArg);
}
}
// Map positional params to the desired arguments
auto curArg = static_cast<int>(startPosArg);
for (std::size_t idx = 0; idx < eatenPosArgs && curArg != -1 && static_cast<size_t>(curArg) < argsInfo.size(); ++ idx, curArg = argsInfo[curArg].nextNotFound)
{
if (argsInfo[curArg].state == Ignored)
continue;
result.args[argsInfo[curArg].info->name] = params.posParams[idx];
argsInfo[curArg].state = Positional;
}
// Fill default arguments (if missing) and check for mandatory
for (std::size_t idx = 0; idx < argsInfo.size(); ++ idx)
{
auto& argInfo = argsInfo[idx];
switch (argInfo.state)
{
case Positional:
case Keyword:
case Ignored:
continue;
case NotFound:
{
if (!IsEmpty(argInfo.info->defaultVal))
{
#if __cplusplus >= 201703L
if constexpr (std::is_same<Result, ParsedArgumentsInfo>::value)
result.args[argInfo.info->name] = std::make_shared<ConstantExpression>(argInfo.info->defaultVal);
else
result.args[argInfo.info->name] = argInfo.info->defaultVal;
#else
result.args[argInfo.info->name] = ParsedArgumentDefaultValGetter<Result>::Get(argInfo.info->defaultVal);
#endif
}
break;
}
case NotFoundMandatory:
isSucceeded = false;
break;
}
}
// Fill the extra positional and kw-args
for (auto& kw : params.kwParams)
{
if (result.args.find(kw.first) != result.args.end())
continue;
result.extraKwArgs[kw.first] = kw.second;
}
for (auto idx = eatenPosArgs; idx < params.posParams.size(); ++ idx)
result.extraPosArgs.push_back(params.posParams[idx]);
return result;
}
ParsedArguments ParseCallParams(const std::initializer_list<ArgumentInfo>& args, const CallParams& params, bool& isSucceeded)
{
return ParseCallParamsImpl<ParsedArguments>(args, params, isSucceeded);
}
ParsedArguments ParseCallParams(const std::vector<ArgumentInfo>& args, const CallParams& params, bool& isSucceeded)
{
return ParseCallParamsImpl<ParsedArguments>(args, params, isSucceeded);
}
ParsedArgumentsInfo ParseCallParamsInfo(const std::initializer_list<ArgumentInfo>& args, const CallParamsInfo& params, bool& isSucceeded)
{
return ParseCallParamsImpl<ParsedArgumentsInfo>(args, params, isSucceeded);
}
ParsedArgumentsInfo ParseCallParamsInfo(const std::vector<ArgumentInfo>& args, const CallParamsInfo& params, bool& isSucceeded)
{
return ParseCallParamsImpl<ParsedArgumentsInfo>(args, params, isSucceeded);
}
CallParams EvaluateCallParams(const CallParamsInfo& info, RenderContext& context)
{
CallParams result;
for (auto& p : info.posParams)
result.posParams.push_back(p->Evaluate(context));
for (auto& kw : info.kwParams)
result.kwParams[kw.first] = kw.second->Evaluate(context);
return result;
}
} // namespace helpers
} // namespace jinja2