#include "statements.h" #include "expression_evaluator.h" #include "template_impl.h" #include "value_visitors.h" #include <boost/core/null_deleter.hpp> #include <string> using namespace std::string_literals; namespace jinja2 { void ForStatement::Render(OutStream& os, RenderContext& values) { InternalValue loopVal = m_value->Evaluate(values); RenderLoop(loopVal, os, values, 0); } void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, RenderContext& values, int level) { auto& context = values.EnterScope(); InternalValueMap loopVar; context["loop"s] = CreateMapAdapter(&loopVar); if (m_isRecursive) { loopVar["operator()"s] = Callable(Callable::GlobalFunc, [this, level](const CallParams& params, OutStream& stream, RenderContext& context) { bool isSucceeded = false; auto parsedParams = helpers::ParseCallParams({ { "var", true } }, params, isSucceeded); if (!isSucceeded) return; auto var = parsedParams["var"]; if (var.IsEmpty()) return; RenderLoop(var, stream, context, level + 1); }); loopVar["depth"s] = static_cast<int64_t>(level + 1); loopVar["depth0"s] = static_cast<int64_t>(level); } bool isConverted = false; auto loopItems = ConvertToList(loopVal, isConverted, false); ListAdapter filteredList; ListAdapter indexedList; ListAccessorEnumeratorPtr enumerator; size_t itemIdx = 0; if (!isConverted) { if (m_elseBody) m_elseBody->Render(os, values); values.ExitScope(); return; } std::optional<size_t> listSize; if (m_ifExpr) { filteredList = CreateFilteredAdapter(loopItems, values); enumerator = filteredList.GetEnumerator(); } else { enumerator = loopItems.GetEnumerator(); listSize = loopItems.GetSize(); } bool isLast = false; auto makeIndexedList = [&enumerator, &listSize, &indexedList, &itemIdx, &isLast] { if (isLast) listSize = itemIdx; InternalValueList items; do { items.push_back(enumerator->GetCurrent()); } while (enumerator->MoveNext()); listSize = itemIdx + items.size() + 1; indexedList = ListAdapter::CreateAdapter(std::move(items)); enumerator = indexedList.GetEnumerator(); isLast = !enumerator->MoveNext(); }; if (listSize) { int64_t itemsNum = static_cast<int64_t>(listSize.value()); loopVar["length"s] = InternalValue(itemsNum); } else { loopVar["length"s] = MakeDynamicProperty([&listSize, &makeIndexedList](const CallParams& /*params*/, RenderContext & /*context*/) -> InternalValue { if (!listSize) makeIndexedList(); return static_cast<int64_t>(listSize.value()); }); } bool loopRendered = false; isLast = !enumerator->MoveNext(); InternalValue prevValue; InternalValue curValue; InternalValue nextValue; loopVar["cycle"s] = static_cast<int64_t>(LoopCycleFn); for (; !isLast; ++itemIdx) { prevValue = std::move(curValue); if (itemIdx != 0) { std::swap(curValue, nextValue); loopVar["previtem"s] = prevValue; } else curValue = enumerator->GetCurrent(); isLast = !enumerator->MoveNext(); if (!isLast) { nextValue = enumerator->GetCurrent(); loopVar["nextitem"s] = nextValue; } else loopVar.erase("nextitem"s); loopRendered = true; loopVar["index"s] = static_cast<int64_t>(itemIdx + 1); loopVar["index0"s] = static_cast<int64_t>(itemIdx); loopVar["first"s] = itemIdx == 0; loopVar["last"s] = isLast; if (m_vars.size() > 1) { const auto& valList = ConvertToList(curValue, isConverted); if (!isConverted) continue; auto b = valList.begin(); auto e = valList.end(); for (auto& varName : m_vars) { if (b == e) continue; context[varName] = *b; ++ b; } } else context[m_vars[0]] = curValue; values.EnterScope(); m_mainBody->Render(os, values); values.ExitScope(); } if (!loopRendered && m_elseBody) m_elseBody->Render(os, values); values.ExitScope(); } ListAdapter ForStatement::CreateFilteredAdapter(const ListAdapter& loopItems, RenderContext& values) const { return ListAdapter::CreateAdapter([e = loopItems.GetEnumerator(), this, &values]() mutable { using ResultType = std::optional<InternalValue>; auto& tempContext = values.EnterScope(); for (bool finish = !e->MoveNext(); !finish; finish = !e->MoveNext()) { auto curValue = e->GetCurrent(); if (m_vars.size() > 1) { for (auto& varName : m_vars) tempContext[varName] = Subscript(curValue, varName, &values); } else { tempContext[m_vars[0]] = curValue; } if (ConvertToBool(m_ifExpr->Evaluate(values))) { values.ExitScope(); return ResultType(std::move(curValue)); } } values.ExitScope(); return ResultType(); }); } void IfStatement::Render(OutStream& os, RenderContext& values) { InternalValue val = m_expr->Evaluate(values); bool isTrue = Apply<visitors::BooleanEvaluator>(val); if (isTrue) { m_mainBody->Render(os, values); return; } for (auto& b : m_elseBranches) { if (b->ShouldRender(values)) { b->Render(os, values); break; } } } bool ElseBranchStatement::ShouldRender(RenderContext& values) const { if (!m_expr) return true; return Apply<visitors::BooleanEvaluator>(m_expr->Evaluate(values)); } void ElseBranchStatement::Render(OutStream& os, RenderContext& values) { m_mainBody->Render(os, values); } void SetStatement::AssignBody(InternalValue body, RenderContext& values) { auto& scope = values.GetCurrentScope(); if (m_fields.size() == 1) scope[m_fields.front()] = std::move(body); else { for (const auto& name : m_fields) scope[name] = Subscript(body, name, &values); } } void SetLineStatement::Render(OutStream&, RenderContext& values) { if (!m_expr) return; AssignBody(m_expr->Evaluate(values), values); } InternalValue SetBlockStatement::RenderBody(RenderContext& values) { TargetString result; auto stream = values.GetRendererCallback()->GetStreamOnString(result); auto innerValues = values.Clone(true); m_body->Render(stream, innerValues); return result; } void SetRawBlockStatement::Render(OutStream&, RenderContext& values) { AssignBody(RenderBody(values), values); } void SetFilteredBlockStatement::Render(OutStream&, RenderContext& values) { if (!m_expr) return; AssignBody(m_expr->Evaluate(RenderBody(values), values), values); } class IBlocksRenderer : public IRendererBase { public: virtual bool HasBlock(const std::string& blockName) = 0; virtual void RenderBlock(const std::string& blockName, OutStream& os, RenderContext& values) = 0; }; void ParentBlockStatement::Render(OutStream& os, RenderContext& values) { RenderContext innerContext = values.Clone(m_isScoped); bool found = false; auto parentTplVal = values.FindValue("$$__parent_template", found); if (!found) { m_mainBody->Render(os, values); return; } bool isConverted = false; auto parentTplsList = ConvertToList(parentTplVal->second, isConverted); if (!isConverted) return; IBlocksRenderer* blockRenderer = nullptr; // static_cast<BlocksRenderer*>(*parentTplPtr); for (auto& tplVal : parentTplsList) { auto ptr = GetIf<RendererPtr>(&tplVal); if (!ptr) continue; auto parentTplPtr = static_cast<IBlocksRenderer*>(ptr->get()); if (parentTplPtr->HasBlock(m_name)) { blockRenderer = parentTplPtr; break; } } if (!blockRenderer) { m_mainBody->Render(os, values); return; } auto& scope = innerContext.EnterScope(); scope["$$__super_block"] = RendererPtr(this, boost::null_deleter()); scope["super"] = Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) { m_mainBody->Render(stream, context); }); if (!m_isScoped) scope["$$__parent_template"] = parentTplsList; blockRenderer->RenderBlock(m_name, os, innerContext); innerContext.ExitScope(); auto& globalScope = values.GetGlobalScope(); auto selfMap = GetIf<MapAdapter>(&globalScope[std::string("self")]); if (!selfMap->HasValue(m_name)) selfMap->SetValue(m_name, MakeWrapped(Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) { Render(stream, context); }))); } void BlockStatement::Render(OutStream& os, RenderContext& values) { m_mainBody->Render(os, values); } template<typename CharT> class ParentTemplateRenderer : public IBlocksRenderer { public: ParentTemplateRenderer(std::shared_ptr<TemplateImpl<CharT>> tpl, ExtendsStatement::BlocksCollection* blocks) : m_template(tpl) , m_blocks(blocks) { } void Render(OutStream& os, RenderContext& values) override { auto& scope = values.GetCurrentScope(); InternalValueList parentTemplates; parentTemplates.push_back(InternalValue(RendererPtr(this, boost::null_deleter()))); bool isFound = false; auto p = values.FindValue("$$__parent_template", isFound); if (isFound) { bool isConverted = false; auto prevTplsList = ConvertToList(p->second, isConverted); if (isConverted) { for (auto& tpl : prevTplsList) parentTemplates.push_back(tpl); } } scope["$$__parent_template"] = ListAdapter::CreateAdapter(std::move(parentTemplates)); m_template->GetRenderer()->Render(os, values); } void RenderBlock(const std::string& blockName, OutStream& os, RenderContext& values) override { auto p = m_blocks->find(blockName); if (p == m_blocks->end()) return; p->second->Render(os, values); } bool HasBlock(const std::string& blockName) override { return m_blocks->count(blockName) != 0; } bool IsEqual(const IComparable& other) const override { auto* val = dynamic_cast<const ParentTemplateRenderer*>(&other); if (!val) return false; if (m_template != val->m_template) return false; if (m_blocks && val->m_blocks && *m_blocks != *(val->m_blocks)) return false; if ((m_blocks && !val->m_blocks) || (!m_blocks && val->m_blocks)) return false; return true; } private: std::shared_ptr<TemplateImpl<CharT>> m_template; ExtendsStatement::BlocksCollection* m_blocks; }; template<typename Result, typename Fn> struct TemplateImplVisitor { // ExtendsStatement::BlocksCollection* m_blocks; const Fn& m_fn; bool m_throwError{}; explicit TemplateImplVisitor(const Fn& fn, bool throwError) : m_fn(fn) , m_throwError(throwError) { } template<typename CharT> Result operator()(nonstd::expected<std::shared_ptr<TemplateImpl<CharT>>, ErrorInfoTpl<CharT>> tpl) const { if (!m_throwError && !tpl) { return Result{}; } else if (!tpl) { throw tpl.error(); } return m_fn(tpl.value()); } Result operator()(EmptyValue) const { return Result(); } }; template<typename Result, typename Fn, typename Arg> Result VisitTemplateImpl(Arg&& tpl, bool throwError, Fn&& fn) { return visit(TemplateImplVisitor<Result, Fn>(fn, throwError), tpl); } template<template<typename T> class RendererTpl, typename CharT, typename... Args> auto CreateTemplateRenderer(std::shared_ptr<TemplateImpl<CharT>> tpl, Args&&... args) { return std::make_shared<RendererTpl<CharT>>(tpl, std::forward<Args>(args)...); } void ExtendsStatement::Render(OutStream& os, RenderContext& values) { if (!m_isPath) { // FIXME: Implement processing of templates return; } auto tpl = values.GetRendererCallback()->LoadTemplate(m_templateName); auto renderer = VisitTemplateImpl<RendererPtr>(tpl, true, [this](auto tplPtr) { return CreateTemplateRenderer<ParentTemplateRenderer>(tplPtr, &m_blocks); }); if (renderer) renderer->Render(os, values); } template<typename CharT> class IncludedTemplateRenderer : public IRendererBase { public: IncludedTemplateRenderer(std::shared_ptr<TemplateImpl<CharT>> tpl, bool withContext) : m_template(tpl) , m_withContext(withContext) { } void Render(OutStream& os, RenderContext& values) override { RenderContext innerContext = values.Clone(m_withContext); if (m_withContext) innerContext.EnterScope(); m_template->GetRenderer()->Render(os, innerContext); if (m_withContext) { auto& innerScope = innerContext.GetCurrentScope(); auto& scope = values.GetCurrentScope(); for (auto& v : innerScope) { scope[v.first] = std::move(v.second); } } } bool IsEqual(const IComparable& other) const override { auto* val = dynamic_cast<const IncludedTemplateRenderer<CharT>*>(&other); if (!val) return false; if (m_template != val->m_template) return false; if (m_withContext != val->m_withContext) return false; return true; } private: std::shared_ptr<TemplateImpl<CharT>> m_template; bool m_withContext{}; }; void IncludeStatement::Render(OutStream& os, RenderContext& values) { auto templateNames = m_expr->Evaluate(values); bool isConverted = false; ListAdapter list = ConvertToList(templateNames, isConverted); auto doRender = [this, &values, &os](auto&& name) -> bool { auto tpl = values.GetRendererCallback()->LoadTemplate(name); try { auto renderer = VisitTemplateImpl<RendererPtr>( tpl, true, [this](auto tplPtr) { return CreateTemplateRenderer<IncludedTemplateRenderer>(tplPtr, m_withContext); }); if (renderer) { renderer->Render(os, values); return true; } } catch (const ErrorInfoTpl<char>& err) { if (err.GetCode() != ErrorCode::FileNotFound) throw; } catch (const ErrorInfoTpl<wchar_t>& err) { if (err.GetCode() != ErrorCode::FileNotFound) throw; } return false; }; bool rendered = false; if (isConverted) { for (auto& name : list) { rendered = doRender(name); if (rendered) break; } } else { rendered = doRender(templateNames); } if (!rendered && !m_ignoreMissing) { InternalValueList files; ValuesList extraParams; if (isConverted) { extraParams.push_back(IntValue2Value(templateNames)); } else { files.push_back(templateNames); extraParams.push_back(IntValue2Value(ListAdapter::CreateAdapter(std::move(files)))); } values.GetRendererCallback()->ThrowRuntimeError(ErrorCode::TemplateNotFound, std::move(extraParams)); } } class ImportedMacroRenderer : public IRendererBase { public: explicit ImportedMacroRenderer(InternalValueMap&& map, bool withContext) : m_importedContext(std::move(map)) , m_withContext(withContext) { } void Render(OutStream& /*os*/, RenderContext& /*values*/) override {} void InvokeMacro(const Callable& callable, const CallParams& params, OutStream& stream, RenderContext& context) { auto ctx = context.Clone(m_withContext); ctx.BindScope(&m_importedContext); callable.GetStatementCallable()(params, stream, ctx); } static void InvokeMacro(const std::string& contextName, const Callable& callable, const CallParams& params, OutStream& stream, RenderContext& context) { bool contextValFound = false; auto contextVal = context.FindValue(contextName, contextValFound); if (!contextValFound) return; auto rendererPtr = GetIf<RendererPtr>(&contextVal->second); if (!rendererPtr) return; auto renderer = static_cast<ImportedMacroRenderer*>(rendererPtr->get()); renderer->InvokeMacro(callable, params, stream, context); } bool IsEqual(const IComparable& other) const override { auto* val = dynamic_cast<const ImportedMacroRenderer*>(&other); if (!val) return false; if (m_importedContext != val->m_importedContext) return false; if (m_withContext != val->m_withContext) return false; return true; } private: InternalValueMap m_importedContext; bool m_withContext{}; }; void ImportStatement::Render(OutStream& /*os*/, RenderContext& values) { auto name = m_nameExpr->Evaluate(values); if (!m_renderer) { auto tpl = values.GetRendererCallback()->LoadTemplate(name); m_renderer = VisitTemplateImpl<RendererPtr>(tpl, true, [](auto tplPtr) { return CreateTemplateRenderer<IncludedTemplateRenderer>(tplPtr, true); }); } if (!m_renderer) return; std::string scopeName; { TargetString tsScopeName = values.GetRendererCallback()->GetAsTargetString(name); scopeName = "$$_imported_" + GetAsSameString(scopeName, tsScopeName).value(); } TargetString str; auto tmpStream = values.GetRendererCallback()->GetStreamOnString(str); RenderContext newContext = values.Clone(m_withContext); InternalValueMap importedScope; { auto& intImportedScope = newContext.EnterScope(); m_renderer->Render(tmpStream, newContext); importedScope = std::move(intImportedScope); } ImportNames(values, importedScope, scopeName); values.GetCurrentScope()[scopeName] = std::static_pointer_cast<IRendererBase>(std::make_shared<ImportedMacroRenderer>(std::move(importedScope), m_withContext)); } void ImportStatement::ImportNames(RenderContext& values, InternalValueMap& importedScope, const std::string& scopeName) const { InternalValueMap importedNs; for (auto& var : importedScope) { if (var.first.empty()) continue; if (var.first[0] == '_') continue; auto mappedP = m_namesToImport.find(var.first); if (!m_namespace && mappedP == m_namesToImport.end()) continue; InternalValue imported; auto callable = GetIf<Callable>(&var.second); if (!callable) { imported = std::move(var.second); } else if (callable->GetKind() == Callable::Macro) { imported = Callable(Callable::Macro, [fn = std::move(*callable), scopeName](const CallParams& params, OutStream& stream, RenderContext& context) { ImportedMacroRenderer::InvokeMacro(scopeName, fn, params, stream, context); }); } else { continue; } if (m_namespace) importedNs[var.first] = std::move(imported); else values.GetCurrentScope()[mappedP->second] = std::move(imported); } if (m_namespace) values.GetCurrentScope()[m_namespace.value()] = CreateMapAdapter(std::move(importedNs)); } std::vector<ArgumentInfo> MacroStatement::PrepareMacroParams(RenderContext& values) { std::vector<ArgumentInfo> preparedParams; for (auto& p : m_params) { ArgumentInfo info(p.paramName, !p.defaultValue); if (p.defaultValue) info.defaultVal = p.defaultValue->Evaluate(values); preparedParams.push_back(std::move(info)); } return preparedParams; } void MacroStatement::Render(OutStream&, RenderContext& values) { auto p = PrepareMacroParams(values); values.GetCurrentScope()[m_name] = Callable(Callable::Macro, [this, params = std::move(p)](const CallParams& callParams, OutStream& stream, RenderContext& context) { InvokeMacroRenderer(params, callParams, stream, context); }); } void MacroStatement::InvokeMacroRenderer(const std::vector<ArgumentInfo>& params, const CallParams& callParams, OutStream& stream, RenderContext& context) { InternalValueMap callArgs; InternalValueMap kwArgs; InternalValueList varArgs; SetupCallArgs(params, callParams, context, callArgs, kwArgs, varArgs); InternalValueList arguments; InternalValueList defaults; for (auto& a : params) { arguments.emplace_back(a.name); defaults.emplace_back(a.defaultVal); } auto& scope = context.EnterScope(); for (auto& a : callArgs) scope[a.first] = std::move(a.second); scope["kwargs"s] = CreateMapAdapter(std::move(kwArgs)); scope["varargs"s] = ListAdapter::CreateAdapter(std::move(varArgs)); scope["name"s] = static_cast<std::string>(m_name); scope["arguments"s] = ListAdapter::CreateAdapter(std::move(arguments)); scope["defaults"s] = ListAdapter::CreateAdapter(std::move(defaults)); m_mainBody->Render(stream, context); context.ExitScope(); } void MacroStatement::SetupCallArgs(const std::vector<ArgumentInfo>& argsInfo, const CallParams& callParams, RenderContext& /* context */, InternalValueMap& callArgs, InternalValueMap& kwArgs, InternalValueList& varArgs) { bool isSucceeded = true; ParsedArguments args = helpers::ParseCallParams(argsInfo, callParams, isSucceeded); for (auto& a : args.args) callArgs[a.first] = std::move(a.second); for (auto& a : args.extraKwArgs) kwArgs[a.first] = std::move(a.second); for (auto& a : args.extraPosArgs) varArgs.push_back(std::move(a)); } void MacroStatement::SetupMacroScope(InternalValueMap&) { ; } void MacroCallStatement::Render(OutStream& os, RenderContext& values) { bool isMacroFound = false; auto macroPtr = values.FindValue(m_macroName, isMacroFound); if (!isMacroFound) return; auto& fnVal = macroPtr->second; const Callable* callable = GetIf<Callable>(&fnVal); if (callable == nullptr || callable->GetType() == Callable::Type::Expression) return; auto& curScope = values.GetCurrentScope(); auto callerP = curScope.find("caller"); bool hasCallerVal = callerP != curScope.end(); InternalValue prevCaller; if (hasCallerVal) prevCaller = callerP->second; auto p = PrepareMacroParams(values); curScope["caller"] = Callable(Callable::Macro, [this, params = std::move(p)](const CallParams& callParams, OutStream& stream, RenderContext& context) { InvokeMacroRenderer(params, callParams, stream, context); }); auto callParams = helpers::EvaluateCallParams(m_callParams, values); callable->GetStatementCallable()(callParams, os, values); if (hasCallerVal) curScope["caller"] = prevCaller; else values.GetCurrentScope().erase("caller"); } void MacroCallStatement::SetupMacroScope(InternalValueMap&) {} void DoStatement::Render(OutStream& /*os*/, RenderContext& values) { m_expr->Evaluate(values); } void WithStatement::Render(OutStream& os, RenderContext& values) { auto innerValues = values.Clone(true); auto& scope = innerValues.EnterScope(); for (auto& var : m_scopeVars) scope[var.first] = var.second->Evaluate(values); m_mainBody->Render(os, innerValues); innerValues.ExitScope(); } void FilterStatement::Render(OutStream& os, RenderContext& values) { TargetString arg; auto argStream = values.GetRendererCallback()->GetStreamOnString(arg); auto innerValues = values.Clone(true); m_body->Render(argStream, innerValues); const auto result = m_expr->Evaluate(std::move(arg), values); os.WriteValue(result); } } // namespace jinja2