#include "yql_expr.h" #include "yql_ast_annotation.h" #include "yql_gc_nodes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace NYql { const TStringBuf ZeroString = ""; const char Dot = '.'; const char Sep = '/'; const TStringBuf PkgPrefix = "pkg"; void ReportError(TExprContext& ctx, const TIssue& issue) { ctx.AddError(issue); } namespace { template const T* FindType(const T& sample, TExprContext& ctx) { const auto it = ctx.TypeSet.find(&sample); return ctx.TypeSet.cend() != it ? static_cast(*it) : nullptr; } template const T* AddType(TExprContext& ctx, Args&&... args) { Y_DEBUG_ABORT_UNLESS(!ctx.Frozen); ctx.TypeNodes.emplace(new T(std::forward(args)...)); const auto ins = ctx.TypeSet.emplace(ctx.TypeNodes.top().get()); return static_cast(*ins.first); } void DumpNode(const TExprNode& node, IOutputStream& out, ui32 level, TNodeSet& visited) { for (ui32 i = 0; i < level; ++i) { out.Write(' '); } out << "#" << node.UniqueId() << " [" << node.Type() << "]"; if (node.Type() == TExprNode::Atom || node.Type() == TExprNode::Callable || node.Type() == TExprNode::Argument) { out << " <" << node.Content() << ">"; } constexpr bool WithTypes = false; constexpr bool WithConstraints = false; constexpr bool WithScope = false; if constexpr (WithTypes) { if (node.GetTypeAnn()) { out << ", " << *node.GetTypeAnn(); } } if constexpr (WithConstraints) { if (node.GetState() >= TExprNode::EState::ConstrComplete) { out << ", " << node.GetConstraintSet(); } } if constexpr (WithScope) { if (const auto scope = node.GetDependencyScope()) { out << ", ("; if (const auto outer = scope->first) { out << '#' << outer->UniqueId(); } else { out << "null"; } out << ','; if (const auto inner = scope->second) { out << '#' << inner->UniqueId(); } else { out << "null"; } out << ')'; } } bool showChildren = true; if (!visited.emplace(&node).second) { if (node.Type() == TExprNode::Callable || node.Type() == TExprNode::List || node.Type() == TExprNode::Lambda || node.Type() == TExprNode::Arguments) { out << " ..."; showChildren = false; } } out << "\n"; if (showChildren) { for (auto& child : node.Children()) { DumpNode(*child, out, level + 1, visited); } } } struct TContext { struct TFrame { THashMap Bindings; THashMap Imports; TExprNode::TListType Return; }; TExprContext& Expr; TVector Frames; TLibraryCohesion Cohesion; std::unordered_set OverrideLibraries; TNodeOnNodeOwnedMap DeepClones; const TAnnotationNodeMap* Annotations = nullptr; IModuleResolver* ModuleResolver = nullptr; IUrlListerManager* UrlListerManager = nullptr; ui32 TypeAnnotationIndex = Max(); TString File; ui16 SyntaxVersion = 0; TContext(TExprContext& expr) : Expr(expr) { } void AddError(const TAstNode& node, const TString& message) { Expr.AddError(TIssue(node.GetPosition(), message)); } void AddInfo(const TAstNode& node, const TString& message) { auto issue = TIssue(node.GetPosition(), message); issue.SetCode(TIssuesIds::INFO, TSeverityIds::S_INFO); Expr.AddError(issue); } TExprNode::TPtr&& ProcessNode(const TAstNode& node, TExprNode::TPtr&& exprNode) { if (TypeAnnotationIndex != Max()) { exprNode->SetTypeAnn(CompileTypeAnnotation(node)); } return std::move(exprNode); } void PushFrame() { Frames.push_back(TFrame()); } void PopFrame() { Frames.pop_back(); } TExprNode::TListType FindBinding(const TStringBuf& name) const { for (auto it = Frames.crbegin(); it != Frames.crend(); ++it) { const auto r = it->Bindings.find(name); if (it->Bindings.cend() != r) return r->second; } return {}; } TString FindImport(const TStringBuf& name) const { for (auto it = Frames.crbegin(); it != Frames.crend(); ++it) { const auto r = it->Imports.find(name); if (it->Imports.cend() != r) return r->second; } return TString(); } const TTypeAnnotationNode* CompileTypeAnnotation(const TAstNode& node) { auto ptr = Annotations->FindPtr(&node); if (!ptr || TypeAnnotationIndex >= ptr->size()) { AddError(node, "Failed to load type annotation"); return nullptr; } return CompileTypeAnnotationNode(*(*ptr)[TypeAnnotationIndex]); } const TTypeAnnotationNode* CompileTypeAnnotationNode(const TAstNode& node) { if (node.IsAtom()) { if (node.GetContent() == TStringBuf(".")) { return nullptr; } else if (node.GetContent() == TStringBuf("Unit")) { return Expr.MakeType(); } else if (node.GetContent() == TStringBuf("World")) { return Expr.MakeType(); } else if (node.GetContent() == TStringBuf("Void")) { return Expr.MakeType(); } else if (node.GetContent() == TStringBuf("Null")) { return Expr.MakeType(); } else if (node.GetContent() == TStringBuf("Generic")) { return Expr.MakeType(); } else if (node.GetContent() == TStringBuf("EmptyList")) { return Expr.MakeType(); } else if (node.GetContent() == TStringBuf("EmptyDict")) { return Expr.MakeType(); } else { AddError(node, TStringBuilder() << "Unknown type annotation: " << node.GetContent()); return nullptr; } } else { if (node.GetChildrenCount() == 0) { AddError(node, "Bad type annotation, expected not empty list"); return nullptr; } if (!node.GetChild(0)->IsAtom()) { AddError(node, "Bad type annotation, first list item must be an atom"); return nullptr; } auto content = node.GetChild(0)->GetContent(); if (content == TStringBuf("Data")) { const auto count = node.GetChildrenCount(); if (!(count == 2 || count == 4) || !node.GetChild(1)->IsAtom()) { AddError(node, "Bad data type annotation"); return nullptr; } auto slot = NUdf::FindDataSlot(node.GetChild(1)->GetContent()); if (!slot) { AddError(node, "Bad data type annotation"); return nullptr; } if (count == 2) { return Expr.MakeType(*slot); } else { if (!(node.GetChild(2)->IsAtom() && node.GetChild(3)->IsAtom())) { AddError(node, "Bad data type annotation"); return nullptr; } auto ann = Expr.MakeType(*slot, node.GetChild(2)->GetContent(), node.GetChild(3)->GetContent()); if (!ann->Validate(node.GetPosition(), Expr)) { return nullptr; } return ann; } } else if (content == TStringBuf("Pg")) { const auto count = node.GetChildrenCount(); if (count != 2 || !node.GetChild(1)->IsAtom()) { AddError(node, "Bad data type annotation"); return nullptr; } auto typeId = NPg::LookupType(TString(node.GetChild(1)->GetContent())).TypeId; return Expr.MakeType(typeId); } else if (content == TStringBuf("List")) { if (node.GetChildrenCount() != 2) { AddError(node, "Bad list type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r) return nullptr; return Expr.MakeType(r); } else if (content == TStringBuf("Stream")) { if (node.GetChildrenCount() != 2) { AddError(node, "Bad stream type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r) return nullptr; return Expr.MakeType(r); } else if (content == TStringBuf("Struct")) { TVector children; for (size_t index = 1; index < node.GetChildrenCount(); ++index) { auto r = CompileTypeAnnotationNode(*node.GetChild(index)); if (!r) return nullptr; if (r->GetKind() != ETypeAnnotationKind::Item) { AddError(node, "Expected item type annotation"); return nullptr; } children.push_back(r->Cast()); } auto ann = Expr.MakeType(children); if (!ann->Validate(node.GetPosition(), Expr)) { return nullptr; } return ann; } else if (content == TStringBuf("Multi")) { TTypeAnnotationNode::TListType children; for (size_t index = 1; index < node.GetChildrenCount(); ++index) { auto r = CompileTypeAnnotationNode(*node.GetChild(index)); if (!r) return nullptr; children.push_back(r); } auto ann = Expr.MakeType(children); if (!ann->Validate(node.GetPosition(), Expr)) { return nullptr; } return ann; } else if (content == TStringBuf("Tuple")) { TTypeAnnotationNode::TListType children; for (size_t index = 1; index < node.GetChildrenCount(); ++index) { auto r = CompileTypeAnnotationNode(*node.GetChild(index)); if (!r) return nullptr; children.push_back(r); } auto ann = Expr.MakeType(children); if (!ann->Validate(node.GetPosition(), Expr)) { return nullptr; } return ann; } else if (content == TStringBuf("Item")) { if (node.GetChildrenCount() != 3 || !node.GetChild(1)->IsAtom()) { AddError(node, "Bad item type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(2)); if (!r) return nullptr; return Expr.MakeType(TString(node.GetChild(1)->GetContent()), r); } else if (content == TStringBuf("Optional")) { if (node.GetChildrenCount() != 2) { AddError(node, "Bad optional type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r) return nullptr; return Expr.MakeType(r); } else if (content == TStringBuf("Type")) { if (node.GetChildrenCount() != 2) { AddError(node, "Bad type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r) return nullptr; return Expr.MakeType(r); } else if (content == TStringBuf("Dict")) { if (node.GetChildrenCount() != 3) { AddError(node, "Bad dict annotation"); return nullptr; } auto r1 = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r1) return nullptr; auto r2 = CompileTypeAnnotationNode(*node.GetChild(2)); if (!r2) return nullptr; auto ann = Expr.MakeType(r1, r2); if (!ann->Validate(node.GetPosition(), Expr)) { return nullptr; } return ann; } else if (content == TStringBuf("Callable")) { if (node.GetChildrenCount() <= 2) { AddError(node, "Bad callable annotation"); return nullptr; } TVector args; size_t optCount = 0; TString payload; if (!node.GetChild(1)->IsList()) { AddError(node, "Bad callable annotation - expected list"); return nullptr; } if (node.GetChild(1)->GetChildrenCount() > 2) { AddError(node, "Bad callable annotation - too many settings nodes"); return nullptr; } if (node.GetChild(1)->GetChildrenCount() > 0) { auto optChild = node.GetChild(1)->GetChild(0); if (!optChild->IsAtom()) { AddError(node, "Bad callable annotation - expected atom"); return nullptr; } if (!TryFromString(optChild->GetContent(), optCount)) { AddError(node, TStringBuilder() << "Bad callable optional args count: " << node.GetChild(1)->GetContent()); return nullptr; } } if (node.GetChild(1)->GetChildrenCount() > 1) { auto payloadChild = node.GetChild(1)->GetChild(1); if (!payloadChild->IsAtom()) { AddError(node, "Bad callable annotation - expected atom"); return nullptr; } payload = payloadChild->GetContent(); } auto retSettings = node.GetChild(2); if (!retSettings->IsList() || retSettings->GetChildrenCount() != 1) { AddError(node, "Bad callable annotation - expected list of size 1"); return nullptr; } auto retType = CompileTypeAnnotationNode(*retSettings->GetChild(0)); if (!retType) return nullptr; for (size_t index = 3; index < node.GetChildrenCount(); ++index) { auto argSettings = node.GetChild(index); if (!argSettings->IsList() || argSettings->GetChildrenCount() < 1 || argSettings->GetChildrenCount() > 3) { AddError(node, "Bad callable annotation - expected list of size 1..3"); return nullptr; } auto r = CompileTypeAnnotationNode(*argSettings->GetChild(0)); if (!r) return nullptr; TCallableExprType::TArgumentInfo arg; arg.Type = r; if (argSettings->GetChildrenCount() > 1) { auto nameChild = argSettings->GetChild(1); if (!nameChild->IsAtom()) { AddError(node, "Bad callable annotation - expected atom"); return nullptr; } arg.Name = nameChild->GetContent(); } if (argSettings->GetChildrenCount() > 2) { auto flagsChild = argSettings->GetChild(2); if (!flagsChild->IsAtom()) { AddError(node, "Bad callable annotation - expected atom"); return nullptr; } if (!TryFromString(flagsChild->GetContent(), arg.Flags)) { AddError(node, "Bad callable annotation - bad integer"); return nullptr; } } args.push_back(arg); } auto ann = Expr.MakeType(retType, args, optCount, payload); if (!ann->Validate(node.GetPosition(), Expr)) { return nullptr; } return ann; } else if (content == TStringBuf("Resource")) { if (node.GetChildrenCount() != 2 || !node.GetChild(1)->IsAtom()) { AddError(node, "Bad resource type annotation"); return nullptr; } return Expr.MakeType(TString(node.GetChild(1)->GetContent())); } else if (content == TStringBuf("Tagged")) { if (node.GetChildrenCount() != 3 || !node.GetChild(2)->IsAtom()) { AddError(node, "Bad tagged type annotation"); return nullptr; } auto type = CompileTypeAnnotationNode(*node.GetChild(1)); if (!type) return nullptr; TString tag(node.GetChild(2)->GetContent()); auto ann = Expr.MakeType(type, tag); if (!ann->Validate(node.GetPosition(), Expr)) { return nullptr; } return ann; } else if (content == TStringBuf("Error")) { if (node.GetChildrenCount() != 5 || !node.GetChild(1)->IsAtom() || !node.GetChild(2)->IsAtom() || !node.GetChild(3)->IsAtom() || !node.GetChild(4)->IsAtom()) { AddError(node, "Bad error type annotation"); return nullptr; } ui32 row; if (!TryFromString(node.GetChild(1)->GetContent(), row)) { AddError(node, TStringBuilder() << "Bad integer: " << node.GetChild(1)->GetContent()); return nullptr; } ui32 column; if (!TryFromString(node.GetChild(2)->GetContent(), column)) { AddError(node, TStringBuilder() << "Bad integer: " << node.GetChild(2)->GetContent()); return nullptr; } auto file = TString(node.GetChild(3)->GetContent()); return Expr.MakeType(TIssue(TPosition(column, row, file), TString(node.GetChild(4)->GetContent()))); } else if (content == TStringBuf("Variant")) { if (node.GetChildrenCount() != 2) { AddError(node, "Bad variant type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r) return nullptr; auto ann = Expr.MakeType(r); if (!ann->Validate(node.GetPosition(), Expr)) { return nullptr; } return ann; } else if (content == TStringBuf("Stream")) { if (node.GetChildrenCount() != 2) { AddError(node, "Bad stream type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r) return nullptr; return Expr.MakeType(r); } else if (content == TStringBuf("Flow")) { if (node.GetChildrenCount() != 2) { AddError(node, "Bad flow type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r) return nullptr; return Expr.MakeType(r); } else if (content == TStringBuf("Block")) { if (node.GetChildrenCount() != 2) { AddError(node, "Bad block type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r) return nullptr; return Expr.MakeType(r); } else if (content == TStringBuf("Scalar")) { if (node.GetChildrenCount() != 2) { AddError(node, "Bad scalar type annotation"); return nullptr; } auto r = CompileTypeAnnotationNode(*node.GetChild(1)); if (!r) return nullptr; return Expr.MakeType(r); } else { AddError(node, TStringBuilder() << "Unknown type annotation"); return nullptr; } } } }; TAstNode* ConvertTypeAnnotationToAst(const TTypeAnnotationNode& annotation, TMemoryPool& pool, bool refAtoms) { switch (annotation.GetKind()) { case ETypeAnnotationKind::Unit: { return TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Unit"), pool); } case ETypeAnnotationKind::Tuple: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Tuple"), pool); TSmallVec children; children.push_back(self); for (auto& child : annotation.Cast()->GetItems()) { children.push_back(ConvertTypeAnnotationToAst(*child, pool, refAtoms)); } return TAstNode::NewList(TPosition(), children.data(), children.size(), pool); } case ETypeAnnotationKind::Struct: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Struct"), pool); TSmallVec children; children.push_back(self); for (auto& child : annotation.Cast()->GetItems()) { children.push_back(ConvertTypeAnnotationToAst(*child, pool, refAtoms)); } return TAstNode::NewList(TPosition(), children.data(), children.size(), pool); } case ETypeAnnotationKind::List: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("List"), pool); auto itemType = ConvertTypeAnnotationToAst(*annotation.Cast()->GetItemType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, itemType); } case ETypeAnnotationKind::Optional: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Optional"), pool); auto itemType = ConvertTypeAnnotationToAst(*annotation.Cast()->GetItemType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, itemType); } case ETypeAnnotationKind::Item: { auto casted = annotation.Cast(); auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Item"), pool); auto name = refAtoms ? TAstNode::NewLiteralAtom(TPosition(), casted->GetName(), pool, TNodeFlags::ArbitraryContent) : TAstNode::NewAtom(TPosition(), casted->GetName(), pool, TNodeFlags::ArbitraryContent); auto itemType = ConvertTypeAnnotationToAst(*casted->GetItemType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, name, itemType); } case ETypeAnnotationKind::Data: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Data"), pool); auto datatype = refAtoms ? TAstNode::NewLiteralAtom(TPosition(), annotation.Cast()->GetName(), pool) : TAstNode::NewAtom(TPosition(), annotation.Cast()->GetName(), pool); if (auto params = dynamic_cast(&annotation)) { auto param1 = refAtoms ? TAstNode::NewLiteralAtom(TPosition(), params->GetParamOne(), pool) : TAstNode::NewAtom(TPosition(), params->GetParamOne(), pool); auto param2 = refAtoms ? TAstNode::NewLiteralAtom(TPosition(), params->GetParamTwo(), pool) : TAstNode::NewAtom(TPosition(), params->GetParamTwo(), pool); return TAstNode::NewList(TPosition(), pool, self, datatype, param1, param2); } return TAstNode::NewList(TPosition(), pool, self, datatype); } case ETypeAnnotationKind::Pg: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Pg"), pool); auto name = TAstNode::NewLiteralAtom(TPosition(), annotation.Cast()->GetName(), pool); return TAstNode::NewList(TPosition(), pool, self, name); } case ETypeAnnotationKind::World: { return TAstNode::NewLiteralAtom(TPosition(), TStringBuf("World"), pool); } case ETypeAnnotationKind::Type: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Type"), pool); auto type = ConvertTypeAnnotationToAst(*annotation.Cast()->GetType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, type); } case ETypeAnnotationKind::Dict: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Dict"), pool); auto dictType = annotation.Cast(); auto keyType = ConvertTypeAnnotationToAst(*dictType->GetKeyType(), pool, refAtoms); auto payloadType = ConvertTypeAnnotationToAst(*dictType->GetPayloadType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, keyType, payloadType); } case ETypeAnnotationKind::Void: { return TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Void"), pool); } case ETypeAnnotationKind::Null: { return TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Null"), pool); } case ETypeAnnotationKind::Callable: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Callable"), pool); auto callable = annotation.Cast(); TSmallVec mainSettings; if (callable->GetOptionalArgumentsCount() > 0 || !callable->GetPayload().empty()) { auto optArgs = TAstNode::NewAtom(TPosition(), ToString(callable->GetOptionalArgumentsCount()), pool); mainSettings.push_back(optArgs); } if (!callable->GetPayload().empty()) { auto payload = TAstNode::NewAtom(TPosition(), callable->GetPayload(), pool, TNodeFlags::ArbitraryContent); mainSettings.push_back(payload); } TSmallVec children; children.push_back(self); children.push_back(TAstNode::NewList(TPosition(), mainSettings.data(), mainSettings.size(), pool)); TSmallVec retSettings; retSettings.push_back(ConvertTypeAnnotationToAst(*callable->GetReturnType(), pool, refAtoms)); children.push_back(TAstNode::NewList(TPosition(), retSettings.data(), retSettings.size(), pool)); for (auto& arg : callable->GetArguments()) { TSmallVec argSettings; argSettings.push_back(ConvertTypeAnnotationToAst(*arg.Type, pool, refAtoms)); if (!arg.Name.empty() || arg.Flags != 0) { auto name = TAstNode::NewAtom(TPosition(), arg.Name, pool, TNodeFlags::ArbitraryContent); argSettings.push_back(name); } if (arg.Flags != 0) { auto flags = TAstNode::NewAtom(TPosition(), ToString(arg.Flags), pool, TNodeFlags::ArbitraryContent); argSettings.push_back(flags); } children.push_back(TAstNode::NewList(TPosition(), argSettings.data(), argSettings.size(), pool)); } return TAstNode::NewList(TPosition(), children.data(), children.size(), pool); } case ETypeAnnotationKind::Generic: { return TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Generic"), pool); } case ETypeAnnotationKind::Resource: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Resource"), pool); auto restype = refAtoms ? TAstNode::NewLiteralAtom(TPosition(), annotation.Cast()->GetTag(), pool, TNodeFlags::ArbitraryContent) : TAstNode::NewAtom(TPosition(), annotation.Cast()->GetTag(), pool, TNodeFlags::ArbitraryContent); return TAstNode::NewList(TPosition(), pool, self, restype); } case ETypeAnnotationKind::Tagged: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Tagged"), pool); auto type = ConvertTypeAnnotationToAst(*annotation.Cast()->GetBaseType(), pool, refAtoms); auto restype = refAtoms ? TAstNode::NewLiteralAtom(TPosition(), annotation.Cast()->GetTag(), pool, TNodeFlags::ArbitraryContent) : TAstNode::NewAtom(TPosition(), annotation.Cast()->GetTag(), pool, TNodeFlags::ArbitraryContent); return TAstNode::NewList(TPosition(), pool, self, type, restype); } case ETypeAnnotationKind::Error: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Error"), pool); const auto& err = annotation.Cast()->GetError(); auto row = TAstNode::NewAtom(TPosition(), ToString(err.Position.Row), pool); auto column = TAstNode::NewAtom(TPosition(), ToString(err.Position.Column), pool); auto file = refAtoms ? TAstNode::NewLiteralAtom(TPosition(), err.Position.File, pool, TNodeFlags::ArbitraryContent) : TAstNode::NewAtom(TPosition(), err.Position.File, pool, TNodeFlags::ArbitraryContent); auto message = refAtoms ? TAstNode::NewLiteralAtom(TPosition(), err.GetMessage(), pool, TNodeFlags::ArbitraryContent) : TAstNode::NewAtom(TPosition(), err.GetMessage(), pool, TNodeFlags::ArbitraryContent); return TAstNode::NewList(TPosition(), pool, self, row, column, file, message); } case ETypeAnnotationKind::Variant: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Variant"), pool); auto underlyingType = ConvertTypeAnnotationToAst(*annotation.Cast()->GetUnderlyingType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, underlyingType); } case ETypeAnnotationKind::Stream: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Stream"), pool); auto itemType = ConvertTypeAnnotationToAst(*annotation.Cast()->GetItemType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, itemType); } case ETypeAnnotationKind::Flow: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Flow"), pool); auto itemType = ConvertTypeAnnotationToAst(*annotation.Cast()->GetItemType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, itemType); } case ETypeAnnotationKind::Multi: { auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Multi"), pool); TSmallVec children; children.push_back(self); for (auto& child : annotation.Cast()->GetItems()) { children.push_back(ConvertTypeAnnotationToAst(*child, pool, refAtoms)); } return TAstNode::NewList(TPosition(), children.data(), children.size(), pool); } case ETypeAnnotationKind::EmptyList: { return TAstNode::NewLiteralAtom(TPosition(), TStringBuf("EmptyList"), pool); } case ETypeAnnotationKind::EmptyDict: { return TAstNode::NewLiteralAtom(TPosition(), TStringBuf("EmptyDict"), pool); } case ETypeAnnotationKind::Block: { auto type = annotation.Cast(); auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Block"), pool); auto itemType = ConvertTypeAnnotationToAst(*type->GetItemType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, itemType); } case ETypeAnnotationKind::Scalar: { auto type = annotation.Cast(); auto self = TAstNode::NewLiteralAtom(TPosition(), TStringBuf("Scalar"), pool); auto itemType = ConvertTypeAnnotationToAst(*type->GetItemType(), pool, refAtoms); return TAstNode::NewList(TPosition(), pool, self, itemType); } case ETypeAnnotationKind::LastType: YQL_ENSURE(false, "Unknown kind: " << annotation.GetKind()); } } TAstNode* AnnotateAstNode(TAstNode* node, const TExprNode* exprNode, ui32 flags, TMemoryPool& pool, bool refAtoms) { if (!flags) return node; TSmallVec children; if (flags & TExprAnnotationFlags::Position) { children.push_back(PositionAsNode(node->GetPosition(), pool)); } if ((flags & TExprAnnotationFlags::Types)) { TAstNode* typeAnn = nullptr; if (exprNode) { YQL_ENSURE(exprNode->GetTypeAnn()); typeAnn = ConvertTypeAnnotationToAst(*exprNode->GetTypeAnn(), pool, refAtoms); } else { typeAnn = TAstNode::NewLiteralAtom(node->GetPosition(), TStringBuf("."), pool); } children.push_back(typeAnn); } children.push_back(node); return TAstNode::NewList(node->GetPosition(), children.data(), children.size(), pool); } bool AddParameterDependencies(const TString& url, const TAstNode& node, TContext& ctx) { auto world = ctx.FindBinding("world"); if (!world.empty()) { TSet names; SubstParameters(url, Nothing(), &names); for (const auto& name : names) { auto nameRef = ctx.FindBinding(name); if (nameRef.empty()) { ctx.AddError(node, TStringBuilder() << "Name not found: " << name); return false; } TExprNode::TListType args = world; args.insert(args.end(), nameRef.begin(), nameRef.end()); auto newWorld = TExprNode::TListType{ ctx.Expr.NewCallable(node.GetPosition(), "Left!", { ctx.Expr.NewCallable(node.GetPosition(), "Cons!", std::move(args)) })}; ctx.Frames.back().Bindings["world"] = newWorld; world = newWorld; } } return true; } TExprNode::TListType Compile(const TAstNode& node, TContext& ctx); TExprNode::TPtr CompileQuote(const TAstNode& node, TContext& ctx) { if (node.IsAtom()) { return ctx.ProcessNode(node, ctx.Expr.NewAtom(node.GetPosition(), TString(node.GetContent()), node.GetFlags())); } else { TExprNode::TListType children; children.reserve(node.GetChildrenCount()); for (ui32 index = 0; index < node.GetChildrenCount(); ++index) { auto r = Compile(*node.GetChild(index), ctx); if (r.empty()) return {}; std::move(r.begin(), r.end(), std::back_inserter(children)); } return ctx.ProcessNode(node, ctx.Expr.NewList(node.GetPosition(), std::move(children))); } } TExprNode::TListType CompileLambda(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() < 2) { ctx.AddError(node, "Expected size of list at least 3."); return {}; } const auto args = node.GetChild(1); if (!args->IsList() || args->GetChildrenCount() != 2 || !args->GetChild(0)->IsAtom() || args->GetChild(0)->GetContent() != TStringBuf("quote") || !args->GetChild(1)->IsList()) { ctx.AddError(node, "Lambda arguments must be a quoted list of atoms"); return {}; } const auto params = args->GetChild(1); for (ui32 index = 0; index < params->GetChildrenCount(); ++index) { if (!params->GetChild(index)->IsAtom()) { ctx.AddError(node, "Lambda arguments must be a quoted list of atoms"); return {}; } } ctx.PushFrame(); TExprNode::TListType argNodes; for (ui32 index = 0; index < params->GetChildrenCount(); ++index) { auto arg = params->GetChild(index); auto lambdaArg = ctx.ProcessNode(*arg, ctx.Expr.NewArgument(arg->GetPosition(), TString(arg->GetContent()))); argNodes.push_back(lambdaArg); auto& binding = ctx.Frames.back().Bindings[arg->GetContent()]; if (!binding.empty()) { ctx.PopFrame(); ctx.AddError(*arg, TStringBuilder() << "Duplicated name of lambda parameter: " << arg->GetContent()); return {}; } binding = {lambdaArg}; } TExprNode::TListType body; body.reserve(node.GetChildrenCount() - 2U); for (auto i = 2U; i < node.GetChildrenCount(); ++i) { auto r = Compile(*node.GetChild(i), ctx); if (r.empty()) return {}; std::move(r.begin(), r.end(), std::back_inserter(body)); } ctx.PopFrame(); auto arguments = ctx.ProcessNode(*args, ctx.Expr.NewArguments(args->GetPosition(), std::move(argNodes))); auto lambda = ctx.ProcessNode(node, ctx.Expr.NewLambda(node.GetPosition(), std::move(arguments), std::move(body))); return {lambda}; } bool CompileSetPackageVersion(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() != 3) { ctx.AddError(node, "Expected list of size 3"); return false; } const auto name = node.GetChild(1); if (name->IsAtom() || name->GetChildrenCount() != 2 || !name->GetChild(0)->IsAtom() || !name->GetChild(1)->IsAtom() || name->GetChild(0)->GetContent() != TStringBuf("quote")) { ctx.AddError(*name, "Expected quoted atom for package name"); return false; } const auto versionNode = node.GetChild(2); if (versionNode->IsAtom() || versionNode->GetChildrenCount() != 2 || !versionNode->GetChild(0)->IsAtom() || !versionNode->GetChild(1)->IsAtom() || versionNode->GetChild(0)->GetContent() != TStringBuf("quote")) { ctx.AddError(*versionNode, "Expected quoted atom for package version"); return false; } ui32 version = 0; if (!versionNode->GetChild(1)->IsAtom() || !TryFromString(versionNode->GetChild(1)->GetContent(), version)) { ctx.AddError(*versionNode, TString("Expected package version as number, bad content ") + versionNode->GetChild(1)->GetContent()); return false; } if (ctx.ModuleResolver && !ctx.ModuleResolver->SetPackageDefaultVersion(TString(name->GetChild(1)->GetContent()), version)) { ctx.AddError(*versionNode, TStringBuilder() << "Unable to specify version " << version << " for package " << name->GetChild(1)->GetContent()); return false; } return true; } TExprNode::TPtr CompileBind(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() != 3) { ctx.AddError(node, "Expected list of size 3"); return nullptr; } const auto name = node.GetChild(1); if (!name->IsAtom()) { ctx.AddError(*name, "Expected atom"); return nullptr; } const auto alias = node.GetChild(2); if (alias->IsAtom() || alias->GetChildrenCount() != 2 || !alias->GetChild(0)->IsAtom() || !alias->GetChild(1)->IsAtom() || alias->GetChild(0)->GetContent() != TStringBuf("quote")) { ctx.AddError(*alias, "Expected quoted pair"); return nullptr; } const auto& aliasValue = alias->GetChild(1)->GetContent(); const auto& moduleName = name->GetContent(); TStringBuilder baseMsg; baseMsg << "Module '" << name->GetContent() << "'"; const auto& import = ctx.FindImport(moduleName); if (import.empty()) { ctx.AddError(*name, baseMsg << " does not exist"); return nullptr; } if (ctx.ModuleResolver) { auto exportsPtr = ctx.ModuleResolver->GetModule(import); if (!exportsPtr) { ctx.AddError(*name, baseMsg << "'" << import << "' does not exist"); return nullptr; } const auto& exports = exportsPtr->Symbols(); const auto ex = exports.find(aliasValue); if (exports.cend() == ex) { ctx.AddError(*alias, baseMsg << " export '" << aliasValue << "' does not exist"); return nullptr; } return ctx.Expr.DeepCopy(*ex->second, exportsPtr->ExprCtx(), ctx.DeepClones, true, false); } else { const auto stub = ctx.Expr.NewAtom(node.GetPosition(), "stub"); ctx.Frames.back().Bindings[name->GetContent()] = {stub}; ctx.Cohesion.Imports[stub.Get()] = std::make_pair(import, TString(aliasValue)); return stub; } } bool CompileLet(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() < 3) { ctx.AddError(node, "Expected size of list at least 3."); return false; } const auto name = node.GetChild(1); if (!name->IsAtom()) { ctx.AddError(*name, "Expected atom"); return false; } TExprNode::TListType bind; bind.reserve(node.GetChildrenCount() - 2U); for (auto i = 2U; i < node.GetChildrenCount(); ++i) { auto r = Compile(*node.GetChild(i), ctx); if (r.empty()) return false; std::move(r.begin(), r.end(), std::back_inserter(bind)); } ctx.Frames.back().Bindings[name->GetContent()] = std::move(bind); return true; } bool CompileImport(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() != 3) { ctx.AddError(node, "Expected list of size 3"); return false; } const auto name = node.GetChild(1); if (!name->IsAtom()) { ctx.AddError(*name, "Expected atom"); return false; } const auto alias = node.GetChild(2); if (!alias->IsListOfSize(2) || !alias->GetChild(0)->IsAtom() || !alias->GetChild(1)->IsAtom() || alias->GetChild(0)->GetContent() != TStringBuf("quote")) { ctx.AddError(node, "Expected quoted pair"); return false; } ctx.Frames.back().Imports[name->GetContent()] = alias->GetChild(1)->GetContent(); return true; } bool CompileExport(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() != 2) { ctx.AddError(node, "Expected list of size 2"); return false; } const auto name = node.GetChild(1); if (!name->IsAtom()) { ctx.AddError(*name, "Expected atom"); return false; } auto r = Compile(*node.GetChild(1), ctx); if (r.size() != 1U) return false; ctx.Cohesion.Exports.Symbols(ctx.Expr)[name->GetContent()] = std::move(r.front()); return true; } bool CompileDeclare(const TAstNode& node, TContext& ctx, bool checkOnly) { if (node.GetChildrenCount() != 3) { ctx.AddError(node, "Expected list of size 3"); return false; } const auto name = node.GetChild(1); if (!name->IsAtom()) { ctx.AddError(*name, "Expected atom"); return false; } TString nameStr = TString(name->GetContent()); if (nameStr.size() < 2) { ctx.AddError(*name, "Parameter name should be at least 2 characters long."); return false; } if (nameStr[0] == '$' && std::isdigit(nameStr[1])) { ctx.AddError(*name, "Parameter name cannot start with digit."); return false; } auto typeExpr = Compile(*node.GetChild(2), ctx); if (typeExpr.size() != 1U) return false; auto typePos = node.GetChild(2)->GetPosition(); auto parameterExpr = ctx.ProcessNode(node, ctx.Expr.NewCallable(typePos, "Parameter", { ctx.Expr.NewAtom(node.GetPosition(), nameStr), std::move(typeExpr.front()) })); bool error = false; if (checkOnly) { auto it = ctx.Frames.back().Bindings.find(nameStr); if (it == ctx.Frames.back().Bindings.end()) { ctx.AddError(*name, TStringBuilder() << "Missing parameter: " << nameStr); return false; } if (it->second.size() != 1 || !it->second.front()->IsCallable("Parameter")) { error = true; } } else { if (!ctx.Frames.back().Bindings.emplace(nameStr, TExprNode::TListType{ std::move(parameterExpr) }).second) { error = true; } } if (error) { ctx.AddError(node, TStringBuilder() << "Declare statement hides previously defined name: " << nameStr); return false; } return true; } bool CompileLibraryDef(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() < 2 || node.GetChildrenCount() > 4) { ctx.AddError(node, "Expected list of size from 2 to 4"); return false; } const auto name = node.GetChild(1); if (!name->IsAtom()) { ctx.AddError(*name, "Expected atom"); return false; } TString url; TString token; if (node.GetChildrenCount() > 2) { const auto file = node.GetChild(2); if (!file->IsAtom()) { ctx.AddError(*file, "Expected atom"); return false; } url = file->GetContent(); if (node.GetChildrenCount() > 3) { const auto tokenNode = node.GetChild(3); if (!tokenNode->IsAtom()) { ctx.AddError(*tokenNode, "Expected atom"); return false; } token = tokenNode->GetContent(); } } if (url && !AddParameterDependencies(url, node, ctx)) { return false; } if (!ctx.ModuleResolver) { return true; } if (url) { if (!ctx.ModuleResolver->AddFromUrl(name->GetContent(), url, token, ctx.Expr, ctx.SyntaxVersion, 0, name->GetPosition())) { return false; } } else { if (!ctx.ModuleResolver->AddFromFile(name->GetContent(), ctx.Expr, ctx.SyntaxVersion, 0, name->GetPosition())) { return false; } } return true; } bool CompilePackageDef(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() < 2 || node.GetChildrenCount() > 4) { ctx.AddError(node, "Expected list of size from 2 to 4"); return false; } auto nameNode = node.GetChild(1); if (!nameNode->IsAtom()) { ctx.AddError(*nameNode, "Expected atom"); return false; } auto name = TString(nameNode->GetContent()); TString url; if (node.GetChildrenCount() > 2) { const auto file = node.GetChild(2); if (!file->IsAtom()) { ctx.AddError(*file, "Expected atom"); return false; } url = file->GetContent(); } TString token; if (node.GetChildrenCount() > 3) { const auto tokenNode = node.GetChild(3); if (!tokenNode->IsAtom()) { ctx.AddError(*tokenNode, "Expected atom"); return false; } token = tokenNode->GetContent(); } if (url && !AddParameterDependencies(url, node, ctx)) { return false; } if (!ctx.ModuleResolver) { return true; } if (!ctx.UrlListerManager) { return true; } ctx.ModuleResolver->RegisterPackage(name); auto packageModuleName = TStringBuilder() << PkgPrefix; TStringBuf nameBuf(name); while (auto part = nameBuf.NextTok(Dot)) { packageModuleName << Sep << part; } auto queue = TVector> { {packageModuleName, url} }; while (queue) { auto [prefix, url] = queue.back(); queue.pop_back(); TVector urlListEntries; try { urlListEntries = ctx.UrlListerManager->ListUrl(url, token); } catch (const std::exception& e) { ctx.AddError(*nameNode, TStringBuilder() << "UrlListerManager: failed to list URL \"" << url << "\", details: " << e.what() ); return false; } for (auto& urlListEntry: urlListEntries) { switch (urlListEntry.Type) { case EUrlListEntryType::FILE: { auto moduleName = TStringBuilder() << prefix << Sep << urlListEntry.Name; if (ctx.OverrideLibraries.contains(moduleName)) { continue; } if (!ctx.ModuleResolver->AddFromUrl( moduleName, urlListEntry.Url, token, ctx.Expr, ctx.SyntaxVersion, 0, nameNode->GetPosition() )) { return false; } break; } case EUrlListEntryType::DIRECTORY: { queue.push_back({ TStringBuilder() << prefix << Sep << urlListEntry.Name, urlListEntry.Url }); break; } } } } return true; } bool CompileOverrideLibraryDef(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() != 2) { ctx.AddError(node, "Expected list of size 2"); return false; } auto nameNode = node.GetChild(1); if (!nameNode->IsAtom()) { ctx.AddError(*nameNode, "Expected atom"); return false; } if (!ctx.ModuleResolver) { return true; } auto overrideLibraryName = TStringBuilder() << PkgPrefix << Sep << nameNode->GetContent(); if (!ctx.ModuleResolver->AddFromFile( overrideLibraryName, ctx.Expr, ctx.SyntaxVersion, 0, nameNode->GetPosition() )) { return false; } ctx.OverrideLibraries.insert(std::move(overrideLibraryName)); return true; } bool CompileReturn(const TAstNode& node, TContext& ctx) { if (node.GetChildrenCount() < 2U) { ctx.AddError(node, "Expected non empty list."); return false; } TExprNode::TListType returns; returns.reserve(node.GetChildrenCount() - 1U); for (auto i = 1U; i < node.GetChildrenCount(); ++i) { auto r = Compile(*node.GetChild(i), ctx); if (r.empty()) return false; std::move(r.begin(), r.end(), std::back_inserter(returns)); } ctx.Frames.back().Return = std::move(returns); return true; } TExprNode::TListType CompileFunction(const TAstNode& root, TContext& ctx, bool topLevel = false) { if (!root.IsList()) { ctx.AddError(root, "Expected list"); return {}; } if (ctx.Frames.size() > 1000U) { ctx.AddError(root, "Too deep graph!"); return {}; } ctx.PushFrame(); if (topLevel) { for (ui32 index = 0; index < root.GetChildrenCount(); ++index) { const auto node = root.GetChild(index); if (!node->IsList()) { ctx.AddError(*node, "Expected list"); return {}; } if (node->GetChildrenCount() == 0) { ctx.AddError(*node, "Expected not empty list"); return {}; } const auto firstChild = node->GetChild(0); if (!firstChild->IsAtom()) { ctx.AddError(*firstChild, "Expected atom"); return {}; } if (firstChild->GetContent() == TStringBuf("library")) { if (!CompileLibraryDef(*node, ctx)) return {}; } else if (firstChild->GetContent() == TStringBuf("set_package_version")) { if (!CompileSetPackageVersion(*node, ctx)) return {}; } else if (firstChild->GetContent() == TStringBuf("declare")) { if (!CompileDeclare(*node, ctx, false)) return {}; } else if (firstChild->GetContent() == TStringBuf("package")) { if (!CompilePackageDef(*node, ctx)) { return {}; } } else if (firstChild->GetContent() == TStringBuf("override_library")) { if (!CompileOverrideLibraryDef(*node, ctx)) { return {}; } } } if (ctx.ModuleResolver) { if (!ctx.ModuleResolver->Link(ctx.Expr)) { return {}; } ctx.ModuleResolver->UpdateNextUniqueId(ctx.Expr); } } for (ui32 index = 0; index < root.GetChildrenCount(); ++index) { const auto node = root.GetChild(index); if (!ctx.Frames.back().Return.empty()) { ctx.Frames.back().Return.clear(); ctx.AddError(*node, "Return is already exist"); return {}; } if (!node->IsList()) { ctx.AddError(*node, "Expected list"); return {}; } if (node->GetChildrenCount() == 0) { ctx.AddError(*node, "Expected not empty list"); return {}; } auto firstChild = node->GetChild(0); if (!firstChild->IsAtom()) { ctx.AddError(*firstChild, "Expected atom"); return {}; } if (firstChild->GetContent() == TStringBuf("let")) { if (!CompileLet(*node, ctx)) return {}; } else if (firstChild->GetContent() == TStringBuf("return")) { if (!CompileReturn(*node, ctx)) return {}; } else if (firstChild->GetContent() == TStringBuf("import")) { if (!CompileImport(*node, ctx)) return {}; } else if (firstChild->GetContent() == TStringBuf("declare")) { if (!topLevel) { ctx.AddError(*firstChild, "Declare statements are only allowed on top level block"); return {}; } if (!CompileDeclare(*node, ctx, true)) return {}; continue; } else if (firstChild->GetContent() == TStringBuf("library")) { if (!topLevel) { ctx.AddError(*firstChild, "Library statements are only allowed on top level block"); return {}; } continue; } else if (firstChild->GetContent() == TStringBuf("set_package_version")) { if (!topLevel) { ctx.AddError(*firstChild, "set_package_version statements are only allowed on top level block"); return {}; } continue; } else if (firstChild->GetContent() == TStringBuf("package")) { if (!topLevel) { ctx.AddError(*firstChild, "Package statements are only allowed on top level block"); return {}; } } else if (firstChild->GetContent() == TStringBuf("override_library")) { if (!topLevel) { ctx.AddError(*firstChild, "override_library statements are only allowed on top level block"); return {}; } } else { ctx.AddError(*firstChild, ToString("expected either let, return or import, but have ") + firstChild->GetContent()); return {}; } } auto ret = std::move(ctx.Frames.back().Return); ctx.PopFrame(); if (ret.empty()) { ctx.AddError(root, "No return found"); } return ret; } bool CompileLibrary(const TAstNode& root, TContext& ctx) { if (!root.IsList()) { ctx.AddError(root, "Expected list"); return false; } ctx.PushFrame(); for (ui32 index = 0; index < root.GetChildrenCount(); ++index) { const auto node = root.GetChild(index); if (!node->IsList()) { ctx.AddError(*node, "Expected list"); return false; } if (node->GetChildrenCount() == 0) { ctx.AddError(*node, "Expected not empty list"); return false; } auto firstChild = node->GetChild(0); if (!firstChild->IsAtom()) { ctx.AddError(*firstChild, "Expected atom"); return false; } if (firstChild->GetContent() == TStringBuf("let")) { if (!CompileLet(*node, ctx)) return false; } else if (firstChild->GetContent() == TStringBuf("import")) { if (!CompileImport(*node, ctx)) return false; } else if (firstChild->GetContent() == TStringBuf("export")) { if (!CompileExport(*node, ctx)) return false; } else { ctx.AddError(*firstChild, "expected either let, export or import"); return false; } } ctx.PopFrame(); return true; } TExprNode::TListType Compile(const TAstNode& node, TContext& ctx) { if (node.IsAtom()) { const auto foundNode = ctx.FindBinding(node.GetContent()); if (foundNode.empty()) { ctx.AddError(node, TStringBuilder() << "Name not found: " << node.GetContent()); return {}; } return foundNode; } if (node.GetChildrenCount() == 0) { ctx.AddError(node, "Empty list, did you forget quote?"); return {}; } if (!node.GetChild(0)->IsAtom()) { ctx.AddError(node, "First item in list is not an atom, did you forget quote?"); return {}; } auto function = node.GetChild(0)->GetContent(); if (function == TStringBuf("quote")) { if (node.GetChildrenCount() != 2) { ctx.AddError(node, "Quote should have one argument"); return {}; } if (auto quote = CompileQuote(*node.GetChild(1), ctx)) return {std::move(quote)}; return {}; } if (function == TStringBuf("let") || function == TStringBuf("return")) { ctx.AddError(node, "Let and return should be used only at first level or inside def"); return {}; } if (function == TStringBuf("lambda")) { return CompileLambda(node, ctx); } if (function == TStringBuf("bind")) { if (auto bind = CompileBind(node, ctx)) return {std::move(bind)}; return {}; } if (function == TStringBuf("block")) { if (node.GetChildrenCount() != 2) { ctx.AddError(node, "Block should have one argument"); return {}; } const auto quotedList = node.GetChild(1); if (quotedList->GetChildrenCount() != 2 || !quotedList->GetChild(0)->IsAtom() || quotedList->GetChild(0)->GetContent() != TStringBuf("quote")) { ctx.AddError(node, "Expected quoted list"); return {}; } return CompileFunction(*quotedList->GetChild(1), ctx); } TExprNode::TListType children; children.reserve(node.GetChildrenCount() - 1U); for (auto index = 1U; index < node.GetChildrenCount(); ++index) { auto r = Compile(*node.GetChild(index), ctx); if (r.empty()) return {}; std::move(r.begin(), r.end(), std::back_inserter(children)); } return {ctx.ProcessNode(node, ctx.Expr.NewCallable(node.GetPosition(), TString(function), std::move(children)))}; } struct TFrameContext { size_t Index = 0; size_t Parent = 0; std::map Nodes; std::vector TopoSortedNodes; TNodeMap Bindings; }; struct TVisitNodeContext { explicit TVisitNodeContext(TExprContext& expr) : Expr(expr) {} TExprContext& Expr; size_t Order = 0ULL; bool RefAtoms = false; bool AllowFreeArgs = false; bool NormalizeAtomFlags = false; TNodeMap FreeArgs; std::unique_ptr Pool; std::vector Frames; TFrameContext* CurrentFrame = nullptr; TNodeMap LambdaFrames; std::map> Parameters; struct TCounters { size_t References = 0ULL, Neighbors = 0ULL, Order = 0ULL, Frame = 0ULL; }; TNodeMap References; const TString& FindBinding(const TExprNode* node) const { for (const auto* frame = CurrentFrame; frame; frame = frame->Index > 0 ? &Frames[frame->Parent] : nullptr) { const auto it = frame->Bindings.find(node); if (frame->Bindings.cend() != it) return it->second; } static const TString stub; return stub; } size_t FindCommonAncestor(size_t one, size_t two) const { while (one && two) { if (one == two) return one; if (one > two) one = Frames[one].Parent; else two = Frames[two].Parent; } return 0ULL; } }; void VisitArguments(const TExprNode& node, TVisitNodeContext& ctx) { YQL_ENSURE(node.Type() == TExprNode::Arguments); for (const auto& arg : node.Children()) { auto& counts = ctx.References[arg.Get()]; ++counts.References; YQL_ENSURE(ctx.CurrentFrame->Nodes.emplace(counts.Order = ++ctx.Order, arg.Get()).second); } } void RevisitNode(const TExprNode& node, TVisitNodeContext& ctx); void RevisitNode(TVisitNodeContext::TCounters& counts, const TExprNode& node, TVisitNodeContext& ctx) { const auto nf = ctx.FindCommonAncestor(ctx.CurrentFrame->Index, counts.Frame); if (counts.Frame != nf) { auto& frame = ctx.Frames[counts.Frame = nf]; frame.Nodes.emplace(counts.Order, &node); if (TExprNode::Lambda == node.Type()) { ctx.Frames[ctx.LambdaFrames[&node]].Parent = counts.Frame; } else { node.ForEachChild([&ctx](const TExprNode& child) { RevisitNode(child, ctx); }); } } } void RevisitNode(const TExprNode& node, TVisitNodeContext& ctx) { if (TExprNode::Argument != node.Type()) { RevisitNode(ctx.References[&node], node, ctx); } } void VisitNode(const TExprNode& node, size_t neighbors, TVisitNodeContext& ctx) { if (TExprNode::Argument == node.Type()) return; auto& counts = ctx.References[&node]; counts.Neighbors += neighbors; if (counts.References++) { RevisitNode(counts, node, ctx); } else { counts.Frame = ctx.CurrentFrame->Index; if (node.Type() == TExprNode::Lambda) { YQL_ENSURE(node.ChildrenSize() > 0U); const auto index = ctx.Frames.size(); if (ctx.LambdaFrames.emplace(&node, index).second) { const auto prevFrameIndex = ctx.CurrentFrame - &ctx.Frames.front(); const auto parentIndex = ctx.CurrentFrame->Index; ctx.Frames.emplace_back(); ctx.CurrentFrame = &ctx.Frames.back(); ctx.CurrentFrame->Index = index; ctx.CurrentFrame->Parent = parentIndex; VisitArguments(node.Head(), ctx); for(ui32 i = 1U; i < node.ChildrenSize(); ++i) { VisitNode(*node.Child(i), node.ChildrenSize() - 1U, ctx); } ctx.CurrentFrame = &ctx.Frames.front() + prevFrameIndex; } } else { node.ForEachChild([&](const TExprNode& child) { VisitNode(child, node.ChildrenSize(), ctx); }); } if (!counts.Order) counts.Order = ++ctx.Order; ctx.CurrentFrame->Nodes.emplace(counts.Order, &node); } } using TRoots = TSmallVec; TAstNode* ConvertFunction(TPositionHandle position, const TRoots& roots, TVisitNodeContext& ctx, ui32 annotationFlags, TMemoryPool& pool); TAstNode* BuildValueNode(const TExprNode& node, TVisitNodeContext& ctx, const TString& topLevelName, ui32 annotationFlags, TMemoryPool& pool, bool useBindings) { TAstNode* res = nullptr; const auto& name = ctx.FindBinding(&node); if (!name.empty() && name != topLevelName && useBindings) { res = TAstNode::NewAtom(ctx.Expr.GetPosition(node.Pos()), name, pool); } else { switch (node.Type()) { case TExprNode::Atom: { auto quote = AnnotateAstNode(&TAstNode::QuoteAtom, nullptr, annotationFlags, pool, ctx.RefAtoms); auto flags = ctx.NormalizeAtomFlags ? TNodeFlags::ArbitraryContent : node.Flags(); auto content = AnnotateAstNode( ctx.RefAtoms ? TAstNode::NewLiteralAtom(ctx.Expr.GetPosition(node.Pos()), node.Content(), pool, flags) : TAstNode::NewAtom(ctx.Expr.GetPosition(node.Pos()), node.Content(), pool, flags), &node, annotationFlags, pool, ctx.RefAtoms); res = TAstNode::NewList(ctx.Expr.GetPosition(node.Pos()), pool, quote, content); break; } case TExprNode::List: { TSmallVec values; for (const auto& child : node.Children()) { values.push_back(BuildValueNode(*child, ctx, topLevelName, annotationFlags, pool, useBindings)); } auto quote = AnnotateAstNode(&TAstNode::QuoteAtom, nullptr, annotationFlags, pool, ctx.RefAtoms); auto list = AnnotateAstNode(TAstNode::NewList( ctx.Expr.GetPosition(node.Pos()), values.data(), values.size(), pool), &node, annotationFlags, pool, ctx.RefAtoms); res = TAstNode::NewList(ctx.Expr.GetPosition(node.Pos()), pool, quote, list); break; } case TExprNode::Callable: { if (node.Content() == "Parameter") { const auto& nameNode = *node.Child(0); const auto& typeNode = *node.Child(1); Y_UNUSED(typeNode); res = TAstNode::NewAtom(ctx.Expr.GetPosition(node.Pos()), nameNode.Content(), pool); auto it = ctx.Parameters.find(nameNode.Content()); if (it != ctx.Parameters.end()) { break; } auto declareAtom = TAstNode::NewLiteralAtom(ctx.Expr.GetPosition(node.Pos()), TStringBuf("declare"), pool); auto nameAtom = ctx.RefAtoms ? TAstNode::NewLiteralAtom(ctx.Expr.GetPosition(nameNode.Pos()), nameNode.Content(), pool) : TAstNode::NewAtom(ctx.Expr.GetPosition(nameNode.Pos()), nameNode.Content(), pool); TSmallVec children; children.push_back(AnnotateAstNode(declareAtom, nullptr, annotationFlags, pool, ctx.RefAtoms)); children.push_back(AnnotateAstNode(nameAtom, nullptr, annotationFlags, pool, ctx.RefAtoms)); children.push_back(BuildValueNode(typeNode, ctx, topLevelName, annotationFlags, pool, false)); auto declareNode = TAstNode::NewList(ctx.Expr.GetPosition(node.Pos()), children.data(), children.size(), pool); declareNode = AnnotateAstNode(declareNode, &node, annotationFlags, pool, ctx.RefAtoms); ctx.Parameters.insert(std::make_pair(nameNode.Content(), std::make_pair(&typeNode, declareNode))); break; } TSmallVec children; children.push_back(AnnotateAstNode( ctx.RefAtoms ? TAstNode::NewLiteralAtom(ctx.Expr.GetPosition(node.Pos()), node.Content(), pool) : TAstNode::NewAtom(ctx.Expr.GetPosition(node.Pos()), node.Content(), pool), nullptr, annotationFlags, pool, ctx.RefAtoms)); for (const auto& child : node.Children()) { children.push_back(BuildValueNode(*child, ctx, topLevelName, annotationFlags, pool, useBindings)); } res = TAstNode::NewList(ctx.Expr.GetPosition(node.Pos()), children.data(), children.size(), pool); break; } case TExprNode::Lambda: { const auto prevFrame = ctx.CurrentFrame; const auto it = ctx.LambdaFrames.find(&node); YQL_ENSURE(it != ctx.LambdaFrames.end()); ctx.CurrentFrame = &ctx.Frames[it->second]; YQL_ENSURE(node.ChildrenSize() > 0U); const auto& args = node.Head(); TSmallVec argsChildren; for (const auto& arg : args.Children()) { const auto& name = ctx.FindBinding(arg.Get()); const auto atom = TAstNode::NewAtom(ctx.Expr.GetPosition(node.Pos()), name, pool); argsChildren.emplace_back(AnnotateAstNode(atom, arg.Get(), annotationFlags, pool, ctx.RefAtoms)); } auto argsNode = TAstNode::NewList(ctx.Expr.GetPosition(args.Pos()), argsChildren.data(), argsChildren.size(), pool); auto argsContainer = TAstNode::NewList(ctx.Expr.GetPosition(args.Pos()), pool, AnnotateAstNode(&TAstNode::QuoteAtom, nullptr, annotationFlags, pool, ctx.RefAtoms), AnnotateAstNode(argsNode, nullptr, annotationFlags, pool, ctx.RefAtoms)); const bool block = ctx.CurrentFrame->Bindings.cend() != std::find_if(ctx.CurrentFrame->Bindings.cbegin(), ctx.CurrentFrame->Bindings.cend(), [](const auto& bind) { return bind.first->Type() != TExprNode::Argument; } ); if (block) { TSmallVec body(node.ChildrenSize() - 1U); for (ui32 i = 0U; i < body.size(); ++i) body[i] = node.Child(i + 1U); const auto blockNode = TAstNode::NewLiteralAtom(ctx.Expr.GetPosition(node.Pos()), TStringBuf("block"), pool); const auto quotedListNode = TAstNode::NewList(ctx.Expr.GetPosition(node.Pos()), pool, AnnotateAstNode(&TAstNode::QuoteAtom, nullptr, annotationFlags, pool, ctx.RefAtoms), ConvertFunction(node.Pos(), body, ctx, annotationFlags, pool)); const auto blockBody = TAstNode::NewList(ctx.Expr.GetPosition(node.Pos()), pool, AnnotateAstNode(blockNode, nullptr, annotationFlags, pool, ctx.RefAtoms), AnnotateAstNode(quotedListNode, nullptr, annotationFlags, pool, ctx.RefAtoms)); res = AnnotateAstNode(blockBody, nullptr, annotationFlags, pool, ctx.RefAtoms); ctx.CurrentFrame = prevFrame; res = TAstNode::NewList(ctx.Expr.GetPosition(node.Pos()), pool, AnnotateAstNode(TAstNode::NewLiteralAtom( ctx.Expr.GetPosition(node.Pos()), TStringBuf("lambda"), pool), nullptr, annotationFlags, pool, ctx.RefAtoms), AnnotateAstNode(argsContainer, &args, annotationFlags, pool, ctx.RefAtoms), res); } else { TSmallVec children(node.ChildrenSize() + 1U); for (ui32 i = 1U; i < node.ChildrenSize(); ++i) { children[i + 1U] = BuildValueNode(*node.Child(i), ctx, topLevelName, annotationFlags, pool, useBindings); } ctx.CurrentFrame = prevFrame; children[0] = AnnotateAstNode(TAstNode::NewLiteralAtom( ctx.Expr.GetPosition(node.Pos()), TStringBuf("lambda"), pool), nullptr, annotationFlags, pool, ctx.RefAtoms); children[1] = AnnotateAstNode(argsContainer, &args, annotationFlags, pool, ctx.RefAtoms); res = TAstNode::NewList(ctx.Expr.GetPosition(node.Pos()), children.data(), children.size(), pool); } break; } case TExprNode::World: res = TAstNode::NewLiteralAtom(ctx.Expr.GetPosition(node.Pos()), TStringBuf("world"), pool); break; case TExprNode::Argument: { YQL_ENSURE(ctx.AllowFreeArgs, "Free arguments are not allowed"); auto iter = ctx.FreeArgs.emplace(&node, ctx.FreeArgs.size()); res = TAstNode::NewLiteralAtom(ctx.Expr.GetPosition(node.Pos()), ctx.Expr.AppendString("_FreeArg" + ToString(iter.first->second)), pool); break; } default: YQL_ENSURE(false, "Unknown type: " << static_cast(node.Type())); } } return AnnotateAstNode(res, &node, annotationFlags, pool, ctx.RefAtoms); } TAstNode* ConvertFunction(TPositionHandle position, const TRoots& roots, TVisitNodeContext& ctx, ui32 annotationFlags, TMemoryPool& pool) { YQL_ENSURE(!roots.empty(), "Missed roots."); TSmallVec children; for (const auto& node : ctx.CurrentFrame->TopoSortedNodes) { const auto& name = ctx.FindBinding(node); if (name.empty() || node->Type() == TExprNode::Arguments || node->Type() == TExprNode::Argument) { continue; } const auto letAtom = TAstNode::NewLiteralAtom(ctx.Expr.GetPosition(node->Pos()), TStringBuf("let"), pool); const auto nameAtom = TAstNode::NewAtom(ctx.Expr.GetPosition(node->Pos()), name, pool); const auto valueNode = BuildValueNode(*node, ctx, name, annotationFlags, pool, true); const auto letNode = TAstNode::NewList(ctx.Expr.GetPosition(node->Pos()), pool, AnnotateAstNode(letAtom, nullptr, annotationFlags, pool, ctx.RefAtoms), AnnotateAstNode(nameAtom, nullptr, annotationFlags, pool, ctx.RefAtoms), valueNode); children.push_back(AnnotateAstNode(letNode, nullptr, annotationFlags, pool, ctx.RefAtoms)); } const auto returnAtom = TAstNode::NewLiteralAtom(ctx.Expr.GetPosition(position), TStringBuf("return"), pool); TSmallVec returnChildren; returnChildren.reserve(roots.size() + 1U); returnChildren.emplace_back(AnnotateAstNode(returnAtom, nullptr, annotationFlags, pool, ctx.RefAtoms)); for (const auto root : roots) { returnChildren.emplace_back(BuildValueNode(*root, ctx, TString(), annotationFlags, pool, true)); } const auto returnList = TAstNode::NewList(ctx.Expr.GetPosition(position), returnChildren.data(), returnChildren.size(), pool); children.emplace_back(AnnotateAstNode(returnList, 1U == roots.size() ? roots.front() : nullptr, annotationFlags, pool, ctx.RefAtoms)); if (!ctx.CurrentFrame->Index && !ctx.Parameters.empty()) { TSmallVec parameterNodes; parameterNodes.reserve(ctx.Parameters.size()); for (auto& pair : ctx.Parameters) { parameterNodes.push_back(pair.second.second); } children.insert(children.begin(), parameterNodes.begin(), parameterNodes.end()); } const auto res = TAstNode::NewList(ctx.Expr.GetPosition(position), children.data(), children.size(), pool); return AnnotateAstNode(res, nullptr, annotationFlags, pool, ctx.RefAtoms); } bool InlineNode(const TExprNode& node, size_t references, size_t neighbors, const TConvertToAstSettings& settings) { if (settings.NoInlineFunc) { if (settings.NoInlineFunc(node)) { return false; } } switch (node.Type()) { case TExprNode::Argument: return false; case TExprNode::Atom: if (const auto flags = node.Flags()) { if ((TNodeFlags::BinaryContent | TNodeFlags::MultilineContent) & flags) return false; else { if (TNodeFlags::ArbitraryContent & flags) return node.Content().length() <= (references == 1U ? 0x40U : 0x10U); else return true; } } else return true; default: if (neighbors < 2U) return true; if (const auto children = node.ChildrenSize()) return references == 1U && children < 3U; else return true; } } typedef std::pair TPairOfNodePotinters; typedef std::unordered_set> TNodesPairSet; typedef TNodeMap> TArgumentsMap; bool CompareExpressions(const TExprNode*& one, const TExprNode*& two, TArgumentsMap& argumentsMap, ui32 level, TNodesPairSet& visited) { const auto ins = visited.emplace(one, two); if (!ins.second) { return true; } if (one->Type() != two->Type()) return false; if (one->ChildrenSize() != two->ChildrenSize()) return false; switch (two->Type()) { case TExprNode::Arguments: { ui32 i1 = 0U, i2 = 0U; one->ForEachChild([&](const TExprNode& arg){ argumentsMap.emplace(&arg, std::make_pair(level, ++i1)); }); two->ForEachChild([&](const TExprNode& arg){ argumentsMap.emplace(&arg, std::make_pair(level, ++i2)); }); return true; } case TExprNode::Argument: if (const auto oneArg = argumentsMap.find(one), twoArg = argumentsMap.find(two); oneArg == twoArg) return argumentsMap.cend() != oneArg || one == two; else if (argumentsMap.cend() != oneArg && argumentsMap.cend() != twoArg) { return oneArg->second == twoArg->second; } return false; case TExprNode::Atom: if (one->GetFlagsToCompare() != two->GetFlagsToCompare()) return false; [[fallthrough]]; // AUTOGENERATED_FALLTHROUGH_FIXME case TExprNode::Callable: if (one->Content() != two->Content()) return false; [[fallthrough]]; // AUTOGENERATED_FALLTHROUGH_FIXME default: break; case TExprNode::Lambda: ++level; } if (const auto childs = one->ChildrenSize()) { const auto& l = one->Children(); const auto& r = two->Children(); for (ui32 i = 0U; i < childs; ++i) { if (!CompareExpressions(one = l[i].Get(), two = r[i].Get(), argumentsMap, level, visited)) { return false; } } } return true; } using TNodeSetPtr = std::shared_ptr; TNodeSetPtr ExcludeFromUnresolved(const TExprNode& args, const TNodeSetPtr& unresolved) { if (!unresolved || unresolved->empty() || args.ChildrenSize() == 0) { return unresolved; } size_t excluded = 0; auto newUnresolved = std::make_shared(*unresolved); for (auto& toExclude : args.Children()) { excluded += newUnresolved->erase(toExclude.Get()); } return excluded ? newUnresolved : unresolved; } TNodeSetPtr MergeUnresolvedArgs(const TNodeSetPtr& one, const TNodeSetPtr& two) { if (!one || one->empty()) { return two; } if (!two || two->empty()) { return one; } const TNodeSetPtr& bigger = (one->size() > two->size()) ? one : two; const TNodeSetPtr& smaller = (one->size() > two->size()) ? two : one; TNodeSetPtr result = std::make_shared(*bigger); bool inserted = false; for (auto& item : *smaller) { if (result->insert(item).second) { inserted = true; } } return inserted ? result : bigger; } TNodeSetPtr CollectUnresolvedArgs(const TExprNode& root, TNodeMap& unresolvedArgs, TNodeSet& allArgs) { auto it = unresolvedArgs.find(&root); if (it != unresolvedArgs.end()) { return it->second; } TNodeSetPtr result; switch (root.Type()) { case TExprNode::Argument: result = std::make_shared(TNodeSet{&root}); break; case TExprNode::Lambda: { if (!root.ChildrenSize()) { ythrow yexception() << "lambda #" << root.UniqueId() << " has " << root.ChildrenSize() << " children"; } const auto& arguments = root.Head(); if (arguments.Type() != TExprNode::Arguments) { ythrow yexception() << "unexpected type of arguments node in lambda #" << root.UniqueId(); } arguments.ForEachChild([&](const TExprNode& arg) { if (arg.Type() != TExprNode::Argument) { ythrow yexception() << "expecting argument, #[" << arg.UniqueId() << "]"; } if (!allArgs.insert(&arg).second) { ythrow yexception() << "argument is duplicated, #[" << arg.UniqueId() << "]"; } }); for (ui32 i = 1U; i < root.ChildrenSize(); ++i) { const auto bodyUnresolvedArgs = CollectUnresolvedArgs(*root.Child(i), unresolvedArgs, allArgs); result = ExcludeFromUnresolved(arguments, bodyUnresolvedArgs); } break; } case TExprNode::Callable: case TExprNode::List: { root.ForEachChild([&](const TExprNode& child) { result = MergeUnresolvedArgs(result, CollectUnresolvedArgs(child, unresolvedArgs, allArgs)); }); break; } case TExprNode::Atom: case TExprNode::World: break; case TExprNode::Arguments: ythrow yexception() << "unexpected free arguments node #[" << root.UniqueId() << "]"; break; } unresolvedArgs[&root] = result; return result; } typedef TNodeMap TRefCountsMap; void CalculateReferences(const TExprNode& node, TRefCountsMap& refCounts) { if (!refCounts[&node]++) for (const auto& child : node.Children()) CalculateReferences(*child, refCounts); } void CheckReferences(const TExprNode& node, TRefCountsMap& refCounts, TNodeSet& visited) { if (visited.emplace(&node).second) { for (const auto& child : node.Children()) { YQL_ENSURE(child->UseCount() == refCounts[child.Get()]); CheckReferences(*child, refCounts, visited); } } } bool GatherParentsImpl(const TExprNode& node, TParentsMap& parentsMap, TNodeSet& visited) { if (node.Type() == TExprNode::Arguments || node.Type() == TExprNode::Atom || node.Type() == TExprNode::World) { return false; } if (!visited.emplace(&node).second) { return true; } node.ForEachChild([&](const TExprNode& child) { if (GatherParentsImpl(child, parentsMap, visited)) { parentsMap[&child].emplace(&node); } }); return true; } } // namespace bool CompileExpr(TAstNode& astRoot, TExprNode::TPtr& exprRoot, TExprContext& ctx, IModuleResolver* resolver, IUrlListerManager* urlListerManager, bool hasAnnotations, ui32 typeAnnotationIndex, ui16 syntaxVersion) { exprRoot.Reset(); TAstNode* cleanRoot = nullptr; TAnnotationNodeMap annotations; const TAnnotationNodeMap* currentAnnotations = nullptr; TAstParseResult cleanupRes; if (!hasAnnotations) { typeAnnotationIndex = Max(); cleanRoot = &astRoot; currentAnnotations = nullptr; } else if (typeAnnotationIndex != Max()) { cleanupRes.Pool = std::make_unique(4096); cleanRoot = ExtractAnnotations(astRoot, annotations, *cleanupRes.Pool); cleanupRes.Root = cleanRoot; currentAnnotations = &annotations; } else { cleanupRes.Pool = std::make_unique(4096); cleanRoot = RemoveAnnotations(astRoot, *cleanupRes.Pool); cleanupRes.Root = cleanRoot; currentAnnotations = nullptr; } if (!cleanRoot) { return false; } TContext compileCtx(ctx); compileCtx.SyntaxVersion = syntaxVersion; compileCtx.Annotations = currentAnnotations; compileCtx.TypeAnnotationIndex = typeAnnotationIndex; compileCtx.ModuleResolver = resolver; compileCtx.UrlListerManager = urlListerManager; compileCtx.PushFrame(); auto world = compileCtx.Expr.NewWorld(astRoot.GetPosition()); if (typeAnnotationIndex != Max()) { world->SetTypeAnn(compileCtx.Expr.MakeType()); } compileCtx.Frames.back().Bindings[TStringBuf("world")] = {std::move(world)}; auto ret = CompileFunction(*cleanRoot, compileCtx, true); if (1U != ret.size()) return false; exprRoot = std::move(ret.front()); compileCtx.PopFrame(); return bool(exprRoot); } bool CompileExpr(TAstNode& astRoot, TExprNode::TPtr& exprRoot, TExprContext& ctx, IModuleResolver* resolver, IUrlListerManager* urlListerManager, ui32 annotationFlags, ui16 syntaxVersion) { bool hasAnnotations = annotationFlags != TExprAnnotationFlags::None; ui32 typeAnnotationIndex = Max(); if (annotationFlags & TExprAnnotationFlags::Types) { bool hasPostions = annotationFlags & TExprAnnotationFlags::Position; typeAnnotationIndex = hasPostions ? 1 : 0; } return CompileExpr(astRoot, exprRoot, ctx, resolver, urlListerManager, hasAnnotations, typeAnnotationIndex, syntaxVersion); } bool CompileExpr(TAstNode& astRoot, TLibraryCohesion& library, TExprContext& ctx, ui16 syntaxVersion) { const TAstNode* cleanRoot = &astRoot; TContext compileCtx(ctx); compileCtx.Annotations = nullptr; compileCtx.TypeAnnotationIndex = Max(); compileCtx.SyntaxVersion = syntaxVersion; const bool ok = CompileLibrary(*cleanRoot, compileCtx); library = compileCtx.Cohesion; return ok; } const TTypeAnnotationNode* CompileTypeAnnotation(const TAstNode& node, TExprContext& ctx) { TContext compileCtx(ctx); return compileCtx.CompileTypeAnnotationNode(node); } template bool IsDependedImpl(const TExprNode& node, const Set& dependences, TNodeSet& visited) { if (!visited.emplace(&node).second) return false; if (dependences.cend() != dependences.find(&node)) return true; for (const auto& child : node.Children()) { if (IsDependedImpl(*child, dependences, visited)) return true; } return false; } namespace { enum EChangeState : ui8 { Unknown = 0, Changed = 1, Unchanged = 2 }; ui64 CalcBloom(const ui64 id) { return 1ULL | (2ULL << (std::hash()(id) % 63ULL)) | (2ULL << (IntHash(id) % 63ULL)) | (2ULL << (FnvHash(&id, sizeof(id)) % 63ULL)) | (2ULL << (MurmurHash(&id, sizeof(id)) % 63ULL)) | (2ULL << (CityHash64(reinterpret_cast(&id), sizeof(id)) % 63ULL)); } inline bool InBloom(const ui64 set, const ui64 bloom) { return (bloom >> 1) == ((bloom & set) >> 1); } EChangeState GetChanges(TExprNode* start, const TNodeOnNodeOwnedMap& replaces, const TNodeMap& localReplaces, TNodeMap& changes, TNodeMap& updatedLambdas); EChangeState DoGetChanges(TExprNode* start, const TNodeOnNodeOwnedMap& replaces, const TNodeMap& localReplaces, TNodeMap& changes, TNodeMap& updatedLambdas) { if (start->GetBloom() & 1ULL) { bool maybe = false; for (const auto& repl : replaces) { if (repl.second && !repl.first->Dead()) { if (TExprNode::Argument != repl.first->Type()) { maybe = true; break; } if (!repl.first->GetBloom()) const_cast(repl.first)->SetBloom(CalcBloom(repl.first->UniqueId())); if (InBloom(start->GetBloom(), repl.first->GetBloom())) { maybe = true; break; } } } if (!maybe) { return EChangeState::Unchanged; } } start->SetBloom(1ULL); ui32 combinedState = EChangeState::Unchanged; bool incompleteBloom = false; start->ForEachChild([&](TExprNode& child) { combinedState |= GetChanges(&child, replaces, localReplaces, changes, updatedLambdas); start->SetBloom(start->GetBloom() | child.GetBloom()); incompleteBloom = incompleteBloom || (child.Type() != TExprNode::Arguments && !child.GetBloom()); }); if (incompleteBloom) { start->SetBloom(0ULL); } return (EChangeState)combinedState; } EChangeState GetChanges(TExprNode* start, const TNodeOnNodeOwnedMap& replaces, const TNodeMap& localReplaces, TNodeMap& changes, TNodeMap& updatedLambdas) { if (start->Type() == TExprNode::Arguments) { return EChangeState::Unchanged; } if (!start->GetBloom() && TExprNode::Argument == start->Type()) { start->SetBloom(CalcBloom(start->UniqueId())); } auto& state = changes[start]; if (state != EChangeState::Unknown) { return state; } if (const auto it = replaces.find(start); it != replaces.cend()) { return state = it->second ? EChangeState::Changed : EChangeState::Unchanged; } if (start->ChildrenSize() == 0) { return state = EChangeState::Unchanged; } if (start->Type() == TExprNode::Lambda) { TNodeOnNodeOwnedMap newReplaces = replaces; start->Head().ForEachChild([&](const TExprNode& arg){ newReplaces[&arg] = {}; }); const auto locIt = localReplaces.find(start); if (locIt != localReplaces.end()) { for (auto& r: locIt->second) { newReplaces[r.first] = r.second; } } state = DoGetChanges(start, newReplaces, localReplaces, changes, updatedLambdas); if ((state & EChangeState::Changed) != 0) { updatedLambdas.emplace(start, false); } return state; } return state = DoGetChanges(start, replaces, localReplaces, changes, updatedLambdas); } template TExprNode::TPtr DoReplace(const TExprNode::TPtr& start, const TNodeOnNodeOwnedMap& replaces, const TNodeOnNodeOwnedMap& argReplaces, const TNodeMap& localReplaces, TNodeMap& changes, TNodeOnNodeOwnedMap& processed, TExprContext& ctx) { auto& target = processed[start.Get()]; if (target) { return target; } TMaybe replace; const auto it = replaces.find(start.Get()); if (it != replaces.end()) { replace = it->second; } const auto argIt = argReplaces.find(start.Get()); if (argIt != argReplaces.end()) { YQL_ENSURE(!replace.Defined()); replace = argIt->second; } if (replace.Defined()) { if (*replace) { return target = ctx.ReplaceNodes(std::move(*replace), argReplaces); } return target = start; } if (start->ChildrenSize() != 0) { auto changeIt = changes.find(start.Get()); YQL_ENSURE(changeIt != changes.end(), "Missing change"); const bool isChanged = (changeIt->second & EChangeState::Changed) != 0; if (isChanged) { if (start->Type() == TExprNode::Lambda) { TNodeOnNodeOwnedMap newArgReplaces = argReplaces; const auto locIt = localReplaces.find(start.Get()); YQL_ENSURE(locIt != localReplaces.end(), "Missing local changes"); for (auto& r: locIt->second) { newArgReplaces[r.first] = r.second; } const auto& args = start->Head(); TExprNode::TListType newArgsList; newArgsList.reserve(args.ChildrenSize()); args.ForEachChild([&](const TExprNode& arg) { const auto argIt = newArgReplaces.find(&arg); YQL_ENSURE(argIt != newArgReplaces.end(), "Missing argument"); processed.emplace(&arg, argIt->second); newArgsList.emplace_back(argIt->second); }); auto newBody = GetLambdaBody(*start); std::for_each(newBody.begin(), newBody.end(), [&](TExprNode::TPtr& node) { node = DoReplace(node, replaces, newArgReplaces, localReplaces, changes, processed, ctx); }); auto newArgs = ctx.NewArguments(start->Pos(), std::move(newArgsList)); if constexpr (KeepTypeAnns) newArgs->SetTypeAnn(ctx.MakeType()); target = ctx.NewLambda(start->Pos(), std::move(newArgs), std::move(newBody)); if constexpr (KeepTypeAnns) target->SetTypeAnn(start->GetTypeAnn()); return target; } else { bool replaced = false; TExprNode::TListType newChildren; newChildren.reserve(start->ChildrenSize()); for (const auto& child : start->Children()) { auto newChild = DoReplace(child, replaces, argReplaces, localReplaces, changes, processed, ctx); if (newChild != child) replaced = true; newChildren.emplace_back(std::move(newChild)); } if (replaced) { target = ctx.ChangeChildren(*start, std::move(newChildren)); if constexpr (KeepTypeAnns) target->SetTypeAnn(start->GetTypeAnn()); return target; } } } } return target = start; } void EnsureNoBadReplaces(const TExprNode& start, const TNodeOnNodeOwnedMap& replaces, TNodeSet&& visited = TNodeSet()) { if (!visited.insert(&start).second) { return; } const auto it = replaces.find(&start); if (it != replaces.end() && it->second) { ythrow yexception() << "Bad replace for node: " << start.UniqueId() << "\n"; } if (start.Type() == TExprNode::Lambda) { TNodeOnNodeOwnedMap newReplaces = replaces; start.Head().ForEachChild([&](const TExprNode& arg){ newReplaces[&arg] = {}; }); start.ForEachChild([&](const TExprNode& child){ EnsureNoBadReplaces(child, newReplaces, std::move(visited)); }); } else { start.ForEachChild([&](const TExprNode& child){ EnsureNoBadReplaces(child, replaces, std::move(visited)); }); } } const bool InternalDebug = false; template TExprNode::TPtr ReplaceNodesImpl(TExprNode::TPtr&& start, const TNodeOnNodeOwnedMap& replaces, TNodeOnNodeOwnedMap& processed, TExprContext& ctx) { if (InternalDebug) { Cerr << "Before\n" << start->Dump() << "\n"; Cerr << "Replaces\n"; ui32 rep = 0; for (auto& x : replaces) { if (x.second) { Cerr << "#" << ++rep << " " << x.first->Dump() << "\n into " << x.second->Dump() << "\n"; } } } TNodeMap changes; TNodeMap updatedLambdas; TNodeMap localReplaces; if ((GetChanges(start.Get(), replaces, localReplaces, changes, updatedLambdas) & EChangeState::Changed) == 0) { return std::move(start); } if (!updatedLambdas.empty()) { for (;;) { changes.clear(); for (auto& x : updatedLambdas) { if (!x.second) { TNodeOnNodeOwnedMap& lambdaReplaces = localReplaces[x.first]; const auto& args = x.first->Head(); args.ForEachChild([&](const TExprNode& arg) { const auto newArg = lambdaReplaces.emplace(&arg, ctx.ShallowCopy(arg)).first->second; if constexpr (KeepTypeAnns) newArg->SetTypeAnn(arg.GetTypeAnn()); }); x.second = true; } } auto prevSize = updatedLambdas.size(); GetChanges(start.Get(), replaces, localReplaces, changes, updatedLambdas); if (updatedLambdas.size() == prevSize) { break; } } } auto ret = DoReplace(start, replaces, {}, localReplaces, changes, processed, ctx); if (InternalDebug) { Cerr << "After\n" << ret->Dump() << "\n"; EnsureNoBadReplaces(*ret, replaces); } return ret; } } TExprNode::TPtr TExprContext::ReplaceNode(TExprNode::TPtr&& start, const TExprNode& src, TExprNode::TPtr dst) { if (start->Type() == TExprNode::Lambda) { const auto& args = start->Head(); auto body = GetLambdaBody(*start); std::optional argIndex; for (ui32 index = 0U; index < args.ChildrenSize(); ++index) { const auto arg = args.Child(index); if (arg == &src) { if (argIndex) { ythrow yexception() << "argument is duplicated, #[" << arg->UniqueId() << "]"; } argIndex = index; } } if (argIndex) { TExprNode::TListType newArgNodes; newArgNodes.reserve(args.ChildrenSize()); TNodeOnNodeOwnedMap replaces(args.ChildrenSize()); for (ui32 i = 0U; i < args.ChildrenSize(); ++i) { const auto arg = args.Child(i); auto newArg = (i == *argIndex) ? dst : ShallowCopy(*arg); YQL_ENSURE(replaces.emplace(arg, newArg).second); newArgNodes.emplace_back(std::move(newArg)); } return NewLambda(start->Pos(), NewArguments(args.Pos(), std::move(newArgNodes)), ReplaceNodes(std::move(body), replaces)); } } else if (&src == start) { return dst; } return ReplaceNodes(std::move(start), {{&src, std::move(dst)}}); } TExprNode::TPtr TExprContext::ReplaceNodes(TExprNode::TPtr&& start, const TNodeOnNodeOwnedMap& replaces) { TNodeOnNodeOwnedMap processed; return replaces.empty() ? std::move(start) : ReplaceNodesImpl(std::move(start), replaces, processed, *this); } template TExprNode::TListType TExprContext::ReplaceNodes(TExprNode::TListType&& starts, const TNodeOnNodeOwnedMap& replaces) { if (!replaces.empty()) { TNodeOnNodeOwnedMap processed; for (auto& node : starts) { node = ReplaceNodesImpl(std::move(node), replaces, processed, *this); } } return std::move(starts); } template TExprNode::TListType TExprContext::ReplaceNodes(TExprNode::TListType&& starts, const TNodeOnNodeOwnedMap& replaces); template TExprNode::TListType TExprContext::ReplaceNodes(TExprNode::TListType&& starts, const TNodeOnNodeOwnedMap& replaces); bool IsDepended(const TExprNode& node, const TNodeSet& dependences) { TNodeSet visited; return !dependences.empty() && IsDependedImpl(node, dependences, visited); } void CheckArguments(const TExprNode& root) { try { TNodeMap unresolvedArgsMap; TNodeSet allArgs; auto rootUnresolved = CollectUnresolvedArgs(root, unresolvedArgsMap, allArgs); if (rootUnresolved && !rootUnresolved->empty()) { TVector ids; for (auto& i : *rootUnresolved) { ids.push_back(i->UniqueId()); } ythrow yexception() << "detected unresolved arguments at top level: #[" << JoinSeq(", ", ids) << "]"; } } catch (yexception& e) { e << "\n" << root.Dump(); throw; } } TAstParseResult ConvertToAst(const TExprNode& root, TExprContext& exprContext, const TConvertToAstSettings& settings) { #ifdef _DEBUG CheckArguments(root); #endif TVisitNodeContext ctx(exprContext); ctx.RefAtoms = settings.RefAtoms; ctx.AllowFreeArgs = settings.AllowFreeArgs; ctx.NormalizeAtomFlags = settings.NormalizeAtomFlags; ctx.Pool = std::make_unique(4096, TMemoryPool::TExpGrow::Instance(), settings.Allocator); ctx.Frames.push_back(TFrameContext()); ctx.CurrentFrame = &ctx.Frames.front(); VisitNode(root, 0ULL, ctx); ui32 uniqueNum = 0; for (auto& frame : ctx.Frames) { ctx.CurrentFrame = &frame; frame.TopoSortedNodes.reserve(frame.Nodes.size()); for (const auto& node : frame.Nodes) { const auto name = ctx.FindBinding(node.second); if (name.empty()) { const auto& ref = ctx.References[node.second]; if (!InlineNode(*node.second, ref.References, ref.Neighbors, settings)) { if (settings.PrintArguments && node.second->IsArgument()) { auto buffer = TStringBuilder() << "$" << ++uniqueNum << "{" << node.second->Content() << ":" << node.second->UniqueId() << "}"; YQL_ENSURE(frame.Bindings.emplace(node.second, buffer).second); } else { char buffer[1 + 10 + 1]; snprintf(buffer, sizeof(buffer), "$%" PRIu32, ++uniqueNum); YQL_ENSURE(frame.Bindings.emplace(node.second, buffer).second); } frame.TopoSortedNodes.emplace_back(node.second); } } } } ctx.CurrentFrame = &ctx.Frames.front(); TAstParseResult result; result.Root = ConvertFunction(exprContext.AppendPosition(TPosition(1, 1)), {&root}, ctx, settings.AnnotationFlags, *ctx.Pool); result.Pool = std::move(ctx.Pool); return result; } TAstParseResult ConvertToAst(const TExprNode& root, TExprContext& exprContext, ui32 annotationFlags, bool refAtoms) { TConvertToAstSettings settings; settings.AnnotationFlags = annotationFlags; settings.RefAtoms = refAtoms; return ConvertToAst(root, exprContext, settings); } TString TExprNode::Dump() const { TNodeSet visited; TStringStream out; DumpNode(*this, out, 0, visited); return out.Str(); } TPosition TExprNode::Pos(const TExprContext& ctx) const { return ctx.GetPosition(Pos()); } TExprNode::TPtr TExprContext::RenameNode(const TExprNode& node, const TStringBuf& name) { const auto newNode = node.ChangeContent(AllocateNextUniqueId(), AppendString(name)); ExprNodes.emplace_back(newNode.Get()); return newNode; } TExprNode::TPtr TExprContext::ShallowCopy(const TExprNode& node) { YQL_ENSURE(node.Type() != TExprNode::Lambda); const auto newNode = node.Clone(AllocateNextUniqueId()); ExprNodes.emplace_back(newNode.Get()); return newNode; } TExprNode::TPtr TExprContext::ShallowCopyWithPosition(const TExprNode& node, TPositionHandle pos) { YQL_ENSURE(node.Type() != TExprNode::Lambda); const auto newNode = node.CloneWithPosition(AllocateNextUniqueId(), pos); ExprNodes.emplace_back(newNode.Get()); return newNode; } TExprNode::TPtr TExprContext::ChangeChildren(const TExprNode& node, TExprNode::TListType&& children) { const auto newNode = node.ChangeChildren(AllocateNextUniqueId(), std::move(children)); ExprNodes.emplace_back(newNode.Get()); return newNode; } TExprNode::TPtr TExprContext::ChangeChild(const TExprNode& node, ui32 index, TExprNode::TPtr&& child) { const auto newNode = node.ChangeChild(AllocateNextUniqueId(), index, std::move(child)); ExprNodes.emplace_back(newNode.Get()); return newNode; } TExprNode::TPtr TExprContext::ExactChangeChildren(const TExprNode& node, TExprNode::TListType&& children) { const auto newNode = node.ChangeChildren(AllocateNextUniqueId(), std::move(children)); newNode->SetTypeAnn(node.GetTypeAnn()); newNode->CopyConstraints(node); newNode->SetState(node.GetState()); newNode->Result = node.Result; ExprNodes.emplace_back(newNode.Get()); return newNode; } TExprNode::TPtr TExprContext::ExactShallowCopy(const TExprNode& node) { YQL_ENSURE(node.Type() != TExprNode::Lambda); const auto newNode = node.Clone(AllocateNextUniqueId()); newNode->SetTypeAnn(node.GetTypeAnn()); newNode->CopyConstraints(node); newNode->SetState(node.GetState()); newNode->Result = node.Result; ExprNodes.emplace_back(newNode.Get()); return newNode; } TExprNode::TListType GetLambdaBody(const TExprNode& node) { switch (node.ChildrenSize()) { case 1U: return {}; case 2U: return {node.TailPtr()}; default: break; } auto body = node.ChildrenList(); body.erase(body.cbegin()); return body; } TExprNode::TPtr TExprContext::DeepCopyLambda(const TExprNode& node, TExprNode::TListType&& body) { YQL_ENSURE(node.IsLambda()); const auto& prevArgs = node.Head(); TNodeOnNodeOwnedMap replaces(prevArgs.ChildrenSize()); TExprNode::TListType newArgNodes; newArgNodes.reserve(prevArgs.ChildrenSize()); prevArgs.ForEachChild([&](const TExprNode& arg) { auto newArg = ShallowCopy(arg); YQL_ENSURE(replaces.emplace(&arg, newArg).second); newArgNodes.emplace_back(std::move(newArg)); }); auto newBody = ReplaceNodes(std::move(body), replaces); return NewLambda(node.Pos(), NewArguments(prevArgs.Pos(), std::move(newArgNodes)), std::move(newBody)); } TExprNode::TPtr TExprContext::DeepCopyLambda(const TExprNode& node, TExprNode::TPtr&& body) { YQL_ENSURE(node.IsLambda()); const auto& prevArgs = node.Head(); TNodeOnNodeOwnedMap replaces(prevArgs.ChildrenSize()); TExprNode::TListType newArgNodes; newArgNodes.reserve(prevArgs.ChildrenSize()); prevArgs.ForEachChild([&](const TExprNode& arg) { auto newArg = ShallowCopy(arg); YQL_ENSURE(replaces.emplace(&arg, newArg).second); newArgNodes.emplace_back(std::move(newArg)); }); auto newBody = ReplaceNodes(body ? TExprNode::TListType{std::move(body)} : GetLambdaBody(node), replaces); return NewLambda(node.Pos(), NewArguments(prevArgs.Pos(), std::move(newArgNodes)), std::move(newBody)); } TExprNode::TPtr TExprContext::FuseLambdas(const TExprNode& outer, const TExprNode& inner) { YQL_ENSURE(outer.IsLambda() && inner.IsLambda()); const auto& outerArgs = outer.Head(); const auto& innerArgs = inner.Head(); TNodeOnNodeOwnedMap innerReplaces(innerArgs.ChildrenSize()); TExprNode::TListType newArgNodes; newArgNodes.reserve(innerArgs.ChildrenSize()); innerArgs.ForEachChild([&](const TExprNode& arg) { auto newArg = ShallowCopy(arg); YQL_ENSURE(innerReplaces.emplace(&arg, newArg).second); newArgNodes.emplace_back(std::move(newArg)); }); auto body = ReplaceNodes(GetLambdaBody(inner), innerReplaces); TExprNode::TListType newBody; auto outerBody = GetLambdaBody(outer); if (outerArgs.ChildrenSize() + 1U == inner.ChildrenSize()) { auto i = 0U; TNodeOnNodeOwnedMap outerReplaces(outerArgs.ChildrenSize()); outerArgs.ForEachChild([&](const TExprNode& arg) { YQL_ENSURE(outerReplaces.emplace(&arg, std::move(body[i++])).second); }); newBody = ReplaceNodes(std::move(outerBody), outerReplaces); } else if (1U == outerArgs.ChildrenSize()) { newBody.reserve(newBody.size() * body.size()); for (auto item : body) { for (auto root : outerBody) { newBody.emplace_back(ReplaceNode(TExprNode::TPtr(root), outerArgs.Head(), TExprNode::TPtr(item))); } } } else { YQL_ENSURE(outerBody.empty(), "Incompatible lambdas for fuse."); } return NewLambda(outer.Pos(), NewArguments(inner.Head().Pos(), std::move(newArgNodes)), std::move(newBody)); } TExprNode::TPtr TExprContext::DeepCopy(const TExprNode& node, TExprContext& nodeCtx, TNodeOnNodeOwnedMap& deepClones, bool internStrings, bool copyTypes, bool copyResult, TCustomDeepCopier customCopier) { const auto ins = deepClones.emplace(&node, nullptr); if (ins.second) { TExprNode::TListType children; children.reserve(node.ChildrenSize()); if (customCopier && customCopier(node, children)) { } else { node.ForEachChild([&](const TExprNode& child) { children.emplace_back(DeepCopy(child, nodeCtx, deepClones, internStrings, copyTypes, copyResult, customCopier)); }); } ++NodeAllocationCounter; auto newNode = TExprNode::NewNode(AppendPosition(nodeCtx.GetPosition(node.Pos())), node.Type(), std::move(children), internStrings ? AppendString(node.Content()) : node.Content(), node.Flags(), AllocateNextUniqueId()); if (copyTypes && node.GetTypeAnn()) { newNode->SetTypeAnn(node.GetTypeAnn()); } if (copyResult && node.IsCallable() && node.HasResult()) { newNode->SetResult(nodeCtx.ShallowCopy(node.GetResult())); } ins.first->second = newNode; ExprNodes.emplace_back(ins.first->second.Get()); } return ins.first->second; } TExprNode::TPtr TExprContext::WrapByCallableIf(bool condition, const TStringBuf& callable, TExprNode::TPtr&& node) { if (!condition) { return node; } const auto pos = node->Pos(); return NewCallable(pos, callable, {std::move(node)}); } TExprNode::TPtr TExprContext::SwapWithHead(const TExprNode& node) { return ChangeChild(node.Head(), 0U, ChangeChild(node, 0U, node.Head().HeadPtr())); } TConstraintSet TExprContext::MakeConstraintSet(const NYT::TNode& serializedConstraints) { const static std::unordered_map> FACTORIES = { {TSortedConstraintNode::Name(), std::mem_fn(&TExprContext::MakeConstraint)}, {TChoppedConstraintNode::Name(), std::mem_fn(&TExprContext::MakeConstraint)}, {TUniqueConstraintNode::Name(), std::mem_fn(&TExprContext::MakeConstraint)}, {TDistinctConstraintNode::Name(), std::mem_fn(&TExprContext::MakeConstraint)}, {TEmptyConstraintNode::Name(), std::mem_fn(&TExprContext::MakeConstraint)}, {TVarIndexConstraintNode::Name(), std::mem_fn(&TExprContext::MakeConstraint)}, {TMultiConstraintNode::Name(), std::mem_fn(&TExprContext::MakeConstraint)}, }; TConstraintSet res; YQL_ENSURE(serializedConstraints.IsMap(), "Unexpected node type with serialize constraints: " << serializedConstraints.GetType()); for (const auto& [key, node]: serializedConstraints.AsMap()) { auto it = FACTORIES.find(key); YQL_ENSURE(it != FACTORIES.cend(), "Unsupported constraint construction: " << key); try { res.AddConstraint((it->second)(*this, node)); } catch (...) { YQL_ENSURE(false, "Error while constructing constraint: " << CurrentExceptionMessage()); } } return res; } TNodeException::TNodeException() : Pos_() { } TNodeException::TNodeException(const TExprNode& node) : Pos_(node.Pos()) { } TNodeException::TNodeException(const TExprNode* node) : Pos_(node ? node->Pos() : TPositionHandle()) { } TNodeException::TNodeException(const TPositionHandle& pos) : Pos_(pos) { } bool ValidateName(TPosition position, TStringBuf name, TStringBuf descr, TExprContext& ctx) { if (name.empty()) { ctx.AddError(TIssue(position, TStringBuilder() << "Empty " << descr << " name is not allowed")); return false; } if (!IsUtf8(name)) { ctx.AddError(TIssue(position, TStringBuilder() << TString(descr).to_title() << " name must be a valid utf-8 byte sequence: " << TString{name}.Quote())); return false; } if (name.size() > 16_KB) { ctx.AddError(TIssue(position, TStringBuilder() << TString(descr).to_title() << " name length must be less than " << 16_KB)); return false; } return true; } bool ValidateName(TPositionHandle position, TStringBuf name, TStringBuf descr, TExprContext& ctx) { return ValidateName(ctx.GetPosition(position), name, descr, ctx); } bool TDataExprParamsType::Validate(TPosition position, TExprContext& ctx) const { if (GetSlot() != EDataSlot::Decimal) { ctx.AddError(TIssue(position, TStringBuilder() << "Only Decimal may contain parameters, but got: " << GetName())); return false; } ui8 precision; if (!TryFromString(GetParamOne(), precision)){ ctx.AddError(TIssue(position, TStringBuilder() << "Invalid decimal precision: " << GetParamOne())); return false; } if (!precision || precision > 35) { ctx.AddError(TIssue(position, TStringBuilder() << "Invalid decimal precision: " << GetParamOne())); return false; } ui8 scale; if (!TryFromString(GetParamTwo(), scale)){ ctx.AddError(TIssue(position, TStringBuilder() << "Invalid decimal scale: " << GetParamTwo())); return false; } if (scale > precision) { ctx.AddError(TIssue(position, TStringBuilder() << "Invalid decimal parameters: (" << GetParamOne() << "," << GetParamTwo() << ").")); return false; } return true; } bool TDataExprParamsType::Validate(TPositionHandle position, TExprContext& ctx) const { return Validate(ctx.GetPosition(position), ctx); } bool TItemExprType::Validate(TPosition position, TExprContext& ctx) const { return ValidateName(position, Name, "member", ctx); } bool TItemExprType::Validate(TPositionHandle position, TExprContext& ctx) const { return Validate(ctx.GetPosition(position), ctx); } TStringBuf TItemExprType::GetCleanName(bool isVirtual) const { if (!isVirtual) { return Name; } YQL_ENSURE(Name.StartsWith(YqlVirtualPrefix)); return Name.SubStr(YqlVirtualPrefix.size()); } const TItemExprType* TItemExprType::GetCleanItem(bool isVirtual, TExprContext& ctx) const { if (!isVirtual) { return this; } YQL_ENSURE(Name.StartsWith(YqlVirtualPrefix)); return ctx.MakeType(Name.SubStr(YqlVirtualPrefix.size()), ItemType); } bool TMultiExprType::Validate(TPosition position, TExprContext& ctx) const { if (Items.size() > Max()) { ctx.AddError(TIssue(position, TStringBuilder() << "Too many elements: " << Items.size())); return false; } return true; } bool TMultiExprType::Validate(TPositionHandle position, TExprContext& ctx) const { return Validate(ctx.GetPosition(position), ctx); } bool TTupleExprType::Validate(TPosition position, TExprContext& ctx) const { if (Items.size() > Max()) { ctx.AddError(TIssue(position, TStringBuilder() << "Too many tuple elements: " << Items.size())); return false; } return true; } bool TTupleExprType::Validate(TPositionHandle position, TExprContext& ctx) const { return Validate(ctx.GetPosition(position), ctx); } bool TStructExprType::Validate(TPosition position, TExprContext& ctx) const { if (Items.size() > Max()) { ctx.AddError(TIssue(position, TStringBuilder() << "Too many struct members: " << Items.size())); return false; } TString lastName; for (auto& item : Items) { if (!item->Validate(position, ctx)) { return false; } if (item->GetName() == lastName) { ctx.AddError(TIssue(position, TStringBuilder() << "Duplicated member: " << lastName)); return false; } lastName = item->GetName(); } return true; } bool TStructExprType::Validate(TPositionHandle position, TExprContext& ctx) const { return Validate(ctx.GetPosition(position), ctx); } bool TVariantExprType::Validate(TPosition position, TExprContext& ctx) const { if (UnderlyingType->GetKind() == ETypeAnnotationKind::Tuple) { if (!UnderlyingType->Cast()->GetSize()) { ctx.AddError(TIssue(position, TStringBuilder() << "Empty tuple is not allowed as underlying type")); return false; } } else if (UnderlyingType->GetKind() == ETypeAnnotationKind::Struct) { if (!UnderlyingType->Cast()->GetSize()) { ctx.AddError(TIssue(position, TStringBuilder() << "Empty struct is not allowed as underlying type")); return false; } } else { ctx.AddError(TIssue(position, TStringBuilder() << "Expected tuple or struct, but got:" << *UnderlyingType)); return false; } return true; } bool TVariantExprType::Validate(TPositionHandle position, TExprContext& ctx) const { return Validate(ctx.GetPosition(position), ctx); } ui32 TVariantExprType::MakeFlags(const TTypeAnnotationNode* underlyingType) { switch (underlyingType->GetKind()) { case ETypeAnnotationKind::Tuple: { const auto tupleType = underlyingType->Cast(); auto ret = CombineFlags(tupleType->GetItems()); if (tupleType->GetSize() > 1) { ret |= TypeHasManyValues; } return ret; } case ETypeAnnotationKind::Struct: { const auto structType = underlyingType->Cast(); auto ret = CombineFlags(structType->GetItems()); if (structType->GetSize() > 1) { ret |= TypeHasManyValues; } return ret; } default: break; } ythrow yexception() << "unexpected underlying type" << *underlyingType; } bool TDictExprType::Validate(TPosition position, TExprContext& ctx) const { if (KeyType->IsHashable() && KeyType->IsEquatable()) { return true; } if (KeyType->IsComparableInternal()) { return true; } ctx.AddError(TIssue(position, TStringBuilder() << "Expected hashable and equatable or internally comparable dict key type, but got: " << *KeyType)); return false; } bool TDictExprType::Validate(TPositionHandle position, TExprContext& ctx) const { return Validate(ctx.GetPosition(position), ctx); } bool TCallableExprType::Validate(TPosition position, TExprContext& ctx) const { if (OptionalArgumentsCount > Arguments.size()) { ctx.AddError(TIssue(position, TStringBuilder() << "Too many optional arguments: " << OptionalArgumentsCount << ", function has only " << Arguments.size() << " arguments")); return false; } for (ui32 index = Arguments.size() - OptionalArgumentsCount; index < Arguments.size(); ++index) { auto type = Arguments[index].Type; if (type->GetKind() != ETypeAnnotationKind::Optional) { ctx.AddError(TIssue(position, TStringBuilder() << "Expected optional type for argument: " << (index + 1) << " because it's an optional argument, but got: " << *type)); return false; } } bool startedNames = false; std::unordered_set usedNames(Arguments.size()); for (ui32 index = 0; index < Arguments.size(); ++index) { bool hasName = !Arguments[index].Name.empty(); if (startedNames) { if (!hasName) { ctx.AddError(TIssue(position, TStringBuilder() << "Unexpected positional argument at position " << (index + 1) << " just after named arguments")); return false; } } else { startedNames = hasName; } if (hasName) { if (!usedNames.insert(Arguments[index].Name).second) { ctx.AddError(TIssue(position, TStringBuilder() << "Duplication of named argument: " << Arguments[index].Name)); return false; } } } return true; } bool TCallableExprType::Validate(TPositionHandle position, TExprContext& ctx) const { return Validate(ctx.GetPosition(position), ctx); } bool TTaggedExprType::Validate(TPosition position, TExprContext& ctx) const { return ValidateName(position, Tag, "tag", ctx); } bool TTaggedExprType::Validate(TPositionHandle position, TExprContext& ctx) const { return Validate(ctx.GetPosition(position), ctx); } const TString& TPgExprType::GetName() const { return NPg::LookupType(TypeId).Name; } ui32 TPgExprType::GetFlags(ui32 typeId) { auto descPtr = &NPg::LookupType(typeId); if (descPtr->ArrayTypeId == descPtr->TypeId) { auto elemType = descPtr->ElementTypeId; descPtr = &NPg::LookupType(elemType); } const auto& desc = *descPtr; ui32 ret = TypeHasManyValues | TypeHasOptional; if ((!desc.SendFuncId || !desc.ReceiveFuncId) && (!desc.OutFuncId || !desc.InFuncId)) { ret |= TypeNonPersistable; } if (!desc.LessProcId || !desc.CompareProcId) { ret |= TypeNonComparable; } if (!desc.EqualProcId || !desc.CompareProcId) { if (desc.TypeId != NPg::UnknownOid) { ret |= TypeNonEquatable; } } if (!desc.HashProcId) { ret |= TypeNonHashable; } static const std::unordered_set PgSupportedPresort = { "bool"sv, "int2"sv, "int4"sv, "int8"sv, "float4"sv, "float8"sv, "bytea"sv, "varchar"sv, "text"sv, "cstring"sv }; if (!PgSupportedPresort.contains(descPtr->Name)) { ret |= TypeNonPresortable; } return ret; } ui64 TPgExprType::GetPgExtensionsMask(ui32 typeId) { auto descPtr = &NPg::LookupType(typeId); return MakePgExtensionMask(descPtr->ExtensionIndex); } ui64 MakePgExtensionMask(ui32 extensionIndex) { if (!extensionIndex) { return 0; } YQL_ENSURE(extensionIndex <= 64); return 1ull << (extensionIndex - 1); } TExprContext::TExprContext(ui64 nextUniqueId) : StringPool(4096) , NextUniqueId(nextUniqueId) , Frozen(false) , PositionSet( 16, [this](TPositionHandle p) { return GetHash(p); }, [this](TPositionHandle a, TPositionHandle b) { return IsEqual(a, b); } ) { auto handle = AppendPosition(TPosition()); YQL_ENSURE(handle.Handle == 0); IssueManager.SetWarningToErrorTreatMessage( "Treat warning as error mode enabled. " "To disable it use \"pragma warning(\"default\", );\""); IssueManager.SetIssueCountLimit(100); } TPositionHandle TExprContext::AppendPosition(const TPosition& pos) { YQL_ENSURE(Positions.size() <= Max(), "Too many positions"); TPositionHandle handle; handle.Handle = (ui32)Positions.size(); Positions.push_back(pos); auto inserted = PositionSet.insert(handle); if (inserted.second) { return handle; } Positions.pop_back(); return *inserted.first; } TPosition TExprContext::GetPosition(TPositionHandle handle) const { YQL_ENSURE(handle.Handle < Positions.size(), "Unknown PositionHandle"); return Positions[handle.Handle]; } TExprContext::~TExprContext() { UnFreeze(); } void TExprContext::Freeze() { for (auto& node : ExprNodes) { node->MarkFrozen(); } Frozen = true; } void TExprContext::UnFreeze() { if (Frozen) { for (auto& node : ExprNodes) { node->MarkFrozen(false); } Frozen = false; } } void TExprContext::Reset() { YQL_ENSURE(!Frozen); IssueManager.Reset(); Step.Reset(); RepeatTransformCounter = 0; } bool TExprContext::IsEqual(TPositionHandle a, TPositionHandle b) const { YQL_ENSURE(a.Handle < Positions.size()); YQL_ENSURE(b.Handle < Positions.size()); return Positions[a.Handle] == Positions[b.Handle]; } size_t TExprContext::GetHash(TPositionHandle p) const { YQL_ENSURE(p.Handle < Positions.size()); const TPosition& pos = Positions[p.Handle]; size_t h = ComputeHash(pos.File); h = CombineHashes(h, NumericHash(pos.Row)); return CombineHashes(h, NumericHash(pos.Column)); } std::string_view TExprContext::GetIndexAsString(ui32 index) { const auto it = Indexes.find(index); if (it != Indexes.cend()) { return it->second; } const auto& newBuf = AppendString(ToString(index)); Indexes.emplace_hint(it, index, newBuf); return newBuf; } template const T* MakeSinglethonType(TExprContext& ctx, Args&&... args) { auto& singleton = std::get(ctx.SingletonTypeCache); if (!singleton) singleton = AddType(ctx, T::MakeHash(args...), std::forward(args)...); return singleton; } const TVoidExprType* TMakeTypeImpl::Make(TExprContext& ctx) { return MakeSinglethonType(ctx); } const TNullExprType* TMakeTypeImpl::Make(TExprContext& ctx) { return MakeSinglethonType(ctx); } const TEmptyListExprType* TMakeTypeImpl::Make(TExprContext& ctx) { return MakeSinglethonType(ctx); } const TEmptyDictExprType* TMakeTypeImpl::Make(TExprContext& ctx) { return MakeSinglethonType(ctx); } const TUnitExprType* TMakeTypeImpl::Make(TExprContext& ctx) { return MakeSinglethonType(ctx); } const TWorldExprType* TMakeTypeImpl::Make(TExprContext& ctx) { return MakeSinglethonType(ctx); } const TGenericExprType* TMakeTypeImpl::Make(TExprContext& ctx) { return MakeSinglethonType(ctx); } const TItemExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TStringBuf& name, const TTypeAnnotationNode* itemType) { const auto hash = TItemExprType::MakeHash(name, itemType); TItemExprType sample(hash, name, itemType); if (const auto found = FindType(sample, ctx)) return found; auto nameStr = ctx.AppendString(name); return AddType(ctx, hash, nameStr, itemType); } const TListExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode* itemType) { const auto hash = TListExprType::MakeHash(itemType); TListExprType sample(hash, itemType); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, itemType); } const TOptionalExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode* itemType) { const auto hash = TOptionalExprType::MakeHash(itemType); TOptionalExprType sample(hash, itemType); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, itemType); } const TVariantExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode* underlyingType) { const auto hash = TVariantExprType::MakeHash(underlyingType); TVariantExprType sample(hash, underlyingType); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, underlyingType); } const TErrorExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TIssue& error) { const auto hash = TErrorExprType::MakeHash(error); TErrorExprType sample(hash, error); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, error); } const TDictExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode* keyType, const TTypeAnnotationNode* payloadType) { const auto hash = TDictExprType::MakeHash(keyType, payloadType); TDictExprType sample(hash, keyType, payloadType); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, keyType, payloadType); } const TTypeExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode* baseType) { const auto hash = TTypeExprType::MakeHash(baseType); TTypeExprType sample(hash, baseType); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, baseType); } const TDataExprType* TMakeTypeImpl::Make(TExprContext& ctx, EDataSlot slot) { const auto hash = TDataExprType::MakeHash(slot); TDataExprType sample(hash, slot); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, slot); } const TPgExprType* TMakeTypeImpl::Make(TExprContext& ctx, ui32 typeId) { const auto hash = TPgExprType::MakeHash(typeId); TPgExprType sample(hash, typeId); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, typeId); } const TDataExprParamsType* TMakeTypeImpl::Make(TExprContext& ctx, EDataSlot slot, const TStringBuf& one, const TStringBuf& two) { const auto hash = TDataExprParamsType::MakeHash(slot, one, two); TDataExprParamsType sample(hash, slot, one, two); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, slot, ctx.AppendString(one), ctx.AppendString(two)); } const TCallableExprType* TMakeTypeImpl::Make( TExprContext& ctx, const TTypeAnnotationNode* returnType, const TVector& arguments, size_t optionalArgumentsCount, const TStringBuf& payload) { const auto hash = TCallableExprType::MakeHash(returnType, arguments, optionalArgumentsCount, payload); TCallableExprType sample(hash, returnType, arguments, optionalArgumentsCount, payload); if (const auto found = FindType(sample, ctx)) return found; TVector newArgs; newArgs.reserve(arguments.size()); for (const auto& x : arguments) { TCallableExprType::TArgumentInfo arg; arg.Type = x.Type; arg.Name = ctx.AppendString(x.Name); arg.Flags = x.Flags; newArgs.emplace_back(arg); } return AddType(ctx, hash, returnType, newArgs, optionalArgumentsCount, ctx.AppendString(payload)); } const TResourceExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TStringBuf& tag) { const auto hash = TResourceExprType::MakeHash(tag); TResourceExprType sample(hash, tag); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, ctx.AppendString(tag)); } const TTaggedExprType* TMakeTypeImpl::Make( TExprContext& ctx, const TTypeAnnotationNode* baseType, const TStringBuf& tag) { const auto hash = TTaggedExprType::MakeHash(baseType, tag); TTaggedExprType sample(hash, baseType, tag); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, baseType, ctx.AppendString(tag)); } const TStructExprType* TMakeTypeImpl::Make( TExprContext& ctx, const TVector& items) { if (items.empty()) return MakeSinglethonType(ctx, items); auto sortedItems = items; Sort(sortedItems, TStructExprType::TItemLess()); const auto hash = TStructExprType::MakeHash(sortedItems); TStructExprType sample(hash, sortedItems); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, sortedItems); } const TMultiExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode::TListType& items) { if (items.empty()) return MakeSinglethonType(ctx, items); const auto hash = TMultiExprType::MakeHash(items); TMultiExprType sample(hash, items); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, items); } const TTupleExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode::TListType& items) { if (items.empty()) return MakeSinglethonType(ctx, items); const auto hash = TTupleExprType::MakeHash(items); TTupleExprType sample(hash, items); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, items); } const TStreamExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode* itemType) { const auto hash = TStreamExprType::MakeHash(itemType); TStreamExprType sample(hash, itemType); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, itemType); } const TFlowExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode* itemType) { const auto hash = TFlowExprType::MakeHash(itemType); TFlowExprType sample(hash, itemType); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, itemType); } const TBlockExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode* itemType) { const auto hash = TBlockExprType::MakeHash(itemType); TBlockExprType sample(hash, itemType); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, itemType); } const TScalarExprType* TMakeTypeImpl::Make(TExprContext& ctx, const TTypeAnnotationNode* itemType) { const auto hash = TScalarExprType::MakeHash(itemType); TScalarExprType sample(hash, itemType); if (const auto found = FindType(sample, ctx)) return found; return AddType(ctx, hash, itemType); } bool CompareExprTrees(const TExprNode*& one, const TExprNode*& two) { TArgumentsMap map; ui32 level = 0; TNodesPairSet visited; return CompareExpressions(one, two, map, level, visited); } bool CompareExprTreeParts(const TExprNode& one, const TExprNode& two, const TNodeMap& argsMap) { TArgumentsMap map; ui32 level = 0; TNodesPairSet visited; map.reserve(argsMap.size()); std::for_each(argsMap.cbegin(), argsMap.cend(), [&](const TNodeMap::value_type& v){ map.emplace(v.first, std::make_pair(0U, v.second)); }); auto l = &one, r = &two; return CompareExpressions(l, r, map, level, visited); } class TCacheKeyBuilder { public: TString Process(const TExprNode& root) { SHA256_Init(&Sha); unsigned char hash[SHA256_DIGEST_LENGTH]; Visit(root); SHA256_Final(hash, &Sha); return TString((const char*)hash, sizeof(hash)); } private: void Visit(const TExprNode& node) { auto [it, inserted] = Visited.emplace(&node, Visited.size()); SHA256_Update(&Sha, &it->second, sizeof(it->second)); if (!inserted) { return; } ui32 type = node.Type(); SHA256_Update(&Sha, &type, sizeof(type)); if (node.Type() == TExprNode::EType::Atom || node.Type() == TExprNode::EType::Callable) { ui32 textLen = node.Content().size(); SHA256_Update(&Sha, &textLen, sizeof(textLen)); SHA256_Update(&Sha, node.Content().data(), textLen); } if (node.Type() == TExprNode::EType::Atom || node.Type() == TExprNode::EType::Argument || node.Type() == TExprNode::EType::World) { return; } ui32 len = node.ChildrenSize(); SHA256_Update(&Sha, &len, sizeof(len)); for (const auto& child : node.Children()) { Visit(*child); } } private: SHA256_CTX Sha; TNodeMap Visited; }; TString MakeCacheKey(const TExprNode& root) { TCacheKeyBuilder builder; return builder.Process(root); } void GatherParents(const TExprNode& node, TParentsMap& parentsMap) { parentsMap.clear(); TNodeSet visisted; GatherParentsImpl(node, parentsMap, visisted); } void CheckCounts(const TExprNode& root) { TRefCountsMap refCounts; CalculateReferences(root, refCounts); TNodeSet visited; CheckReferences(root, refCounts, visited); } TString SubstParameters(const TString& str, const TMaybe& params, TSet* usedNames) { size_t pos = 0; try { TStringBuilder res; bool insideBrackets = false; TStringBuilder paramBuilder; for (char c : str) { if (c == '{') { if (insideBrackets) { throw yexception() << "Unpexpected {"; } insideBrackets = true; continue; } if (c == '}') { if (!insideBrackets) { throw yexception() << "Unexpected }"; } insideBrackets = false; TString param = paramBuilder; paramBuilder.clear(); if (usedNames) { usedNames->insert(param); } if (params) { const auto& map = params->AsMap(); auto it = map.find(param); if (it == map.end()) { throw yexception() << "No such parameter: '" << param << "'"; } const auto& value = it->second["Data"]; if (!value.IsString()) { throw yexception() << "Parameter value must be a string"; } res << value.AsString(); } continue; } if (insideBrackets) { paramBuilder << c; } else { res << c; } ++pos; } if (insideBrackets) { throw yexception() << "Missing }"; } return res; } catch (yexception& e) { throw yexception() << "Failed to substitute parameters into url: " << str << ", reason:" << e.what() << ", position: " << pos; } } const TTypeAnnotationNode* GetSeqItemType(const TTypeAnnotationNode* type) { if (!type) return nullptr; switch (type->GetKind()) { case ETypeAnnotationKind::List: return type->Cast()->GetItemType(); case ETypeAnnotationKind::Flow: return type->Cast()->GetItemType(); case ETypeAnnotationKind::Stream: return type->Cast()->GetItemType(); case ETypeAnnotationKind::Optional: return type->Cast()->GetItemType(); default: break; } return nullptr; } const TTypeAnnotationNode& GetSeqItemType(const TTypeAnnotationNode& type) { if (const auto itemType = GetSeqItemType(&type)) return *itemType; throw yexception() << "Impossible to get item type from " << type; } const TTypeAnnotationNode& RemoveOptionality(const TTypeAnnotationNode& type) { return ETypeAnnotationKind::Optional == type.GetKind() ? *type.Cast()->GetItemType() : type; } TMaybe NormalizeName(TPosition position, TString& name) { const ui32 inputLength = name.length(); ui32 startCharPos = 0; ui32 totalSkipped = 0; bool atStart = true; bool justSkippedUnderscore = false; for (ui32 i = 0; i < inputLength; ++i) { const char c = name.at(i); if (c == '_') { if (!atStart) { if (justSkippedUnderscore) { return TIssue(position, TStringBuilder() << "\"" << name << "\" looks weird, has multiple consecutive underscores"); } justSkippedUnderscore = true; ++totalSkipped; continue; } else { ++startCharPos; } } else { atStart = false; justSkippedUnderscore = false; } } if (totalSkipped >= 5) { return TIssue(position, TStringBuilder() << "\"" << name << "\" looks weird, has multiple consecutive underscores"); } ui32 outPos = startCharPos; for (ui32 i = startCharPos; i < inputLength; i++) { const char c = name.at(i); if (c == '_') { continue; } else { name[outPos] = AsciiToLower(c); ++outPos; } } name.resize(outPos); Y_ABORT_UNLESS(inputLength - outPos == totalSkipped); return Nothing(); } TString NormalizeName(const TStringBuf& name) { TString result(name); TMaybe error = NormalizeName(TPosition(), result); YQL_ENSURE(error.Empty(), "" << error->GetMessage()); return result; } } // namespace NYql template<> void Out(class IOutputStream &o, NYql::TExprNode::EType x) { #define YQL_EXPR_NODE_TYPE_MAP_TO_STRING_IMPL(name, ...) \ case NYql::TExprNode::name: \ o << #name; \ return; switch (x) { YQL_EXPR_NODE_TYPE_MAP(YQL_EXPR_NODE_TYPE_MAP_TO_STRING_IMPL) default: o << static_cast(x); return; } } template<> void Out(class IOutputStream &o, NYql::TExprNode::EState x) { #define YQL_EXPR_NODE_STATE_MAP_TO_STRING_IMPL(name, ...) \ case NYql::TExprNode::EState::name: \ o << #name; \ return; switch (x) { YQL_EXPR_NODE_STATE_MAP(YQL_EXPR_NODE_STATE_MAP_TO_STRING_IMPL) default: o << static_cast(x); return; } }