// Copyright Epic Games, Inc. All Rights Reserved. #include "Desugarer.h" #include "uLang/Common/Containers/SharedPointer.h" #include "uLang/Common/Containers/UniquePointer.h" #include "uLang/Diagnostics/Glitch.h" #include "uLang/Parser/ParserPass.h" #include "uLang/Semantics/Expression.h" #include "uLang/Semantics/SemanticClass.h" #include "uLang/Semantics/UnknownType.h" #include "uLang/SourceProject/UploadedAtFNVersion.h" #include "uLang/SourceProject/VerseVersion.h" #include "uLang/Syntax/VstNode.h" #include "uLang/Syntax/vsyntax_types.h" namespace { namespace Vst = Verse::Vst; using namespace uLang; class CDesugarerImpl { public: CDesugarerImpl(CSymbolTable& Symbols, CDiagnostics& Diagnostics): _Symbols(Symbols), _Diagnostics(Diagnostics) {} TSRef DesugarProject(const Vst::Project& VstProject) { // Desugar all the project's packages (and build vertex array for Tarjan's algorithm). struct SPackageVertex { TSRef Package; TArray Dependencies; // Indices of vertices this package depends on int32_t DepthIndex = IndexNone; int32_t LowLink = IndexNone; bool bOnStack = false; }; TArray Vertices; Vertices.Reserve(VstProject.GetChildCount()); for (const TSRef& VstPackage : VstProject.GetChildren()) { ULANG_ASSERTF(VstPackage->GetElementType() == Vst::NodeType::Package, "Toolchain must ensure that a project only ever contains packages."); Vertices.Add({ DesugarPackage(static_cast(*VstPackage)), {} }); } // Populate the dependencies for both the Tarjan vertices and the AST packages for (SPackageVertex& Vertex : Vertices) { const Vst::Package* VstPackage = static_cast(Vertex.Package->GetMappedVstNode()); if (ULANG_ENSUREF(VstPackage && VstPackage->GetElementType() == Vst::NodeType::Package, "Node should have been properly mapped by DesugarPackage.")) { Vertex.Dependencies.Reserve(VstPackage->_DependencyPackages.Num()); Vertex.Package->_Dependencies.Reserve(VstPackage->_DependencyPackages.Num()); for (const CUTF8String& DependencyName : VstPackage->_DependencyPackages) { int32_t DependencyIndex = Vertices.IndexOfByPredicate([&DependencyName](const SPackageVertex& DependencyVertex) { return DependencyVertex.Package->_Name == DependencyName; }); if (DependencyIndex != IndexNone) { Vertex.Dependencies.Add(DependencyIndex); Vertex.Package->_Dependencies.Add(Vertices[DependencyIndex].Package); } else if (Vertex.Package->_Role != ConstraintPackageRole) { AppendGlitch(VstPackage, EDiagnostic::ErrSemantic_UnknownPackageDependency, CUTF8String("Package `%s` specifies dependency `%s` which does not exist.", *Vertex.Package->_Name, *DependencyName)); } } } } // Prepare new AST project TSRef AstProject = TSRef::New(VstProject._Name); AstProject->ReserveCompilationUnits(VstProject.GetChildCount()); // Run Tarjan's algorithm to generate the compilation units (SCCs) TArray Stack; Stack.Reserve(Vertices.Num()); int32_t CurrentDepthIndex = 0; auto StrongConnect = [&AstProject, &Vertices, &Stack, &CurrentDepthIndex](int32_t V, const auto& StrongConnectRef) -> void { // Set the depth index for V to the smallest unused index SPackageVertex& Vertex = Vertices[V]; Vertex.DepthIndex = Vertex.LowLink = CurrentDepthIndex++; Stack.Push(V); Vertex.bOnStack = true; // Consider dependencies of V for (int32_t W : Vertex.Dependencies) { SPackageVertex& DependencyVertex = Vertices[W]; if (DependencyVertex.DepthIndex == IndexNone) { // Dependency W has not yet been visited - recurse on it StrongConnectRef(W, StrongConnectRef); Vertex.LowLink = CMath::Min(Vertex.LowLink, DependencyVertex.LowLink); } else if (DependencyVertex.bOnStack) { // Dependency W is in stack and hence in the current SCC // If W is not on stack, then (V, W) is an edge pointing to an SCC already found and must be ignored // The next line may look odd - but is correct. // It says DependencyVertex.DepthIndex not DependencyVertex.LowLink - that is deliberate and from the original paper Vertex.LowLink = CMath::Min(Vertex.LowLink, DependencyVertex.DepthIndex); } } // If V is a root node, pop the stack and generate an SCC if (Vertex.LowLink == Vertex.DepthIndex) { // Since Tarjan's algorithm does a depth-first search, compilation units // will be generated in depth-first order which is the order we desire // therefore no explicit sorting of compilation units is required after this algorithm is done TSRef CompilationUnit = TSRef::New(); int32_t W; do { W = Stack.Pop(); SPackageVertex& SCCVertex = Vertices[W]; SCCVertex.bOnStack = false; SCCVertex.Package->_CompilationUnit = CompilationUnit; CompilationUnit->AppendPackage(Move(SCCVertex.Package)); } while (W != V); AstProject->AppendCompilationUnit(Move(CompilationUnit)); } }; for (int32_t Index = 0; Index < Vertices.Num(); ++Index) { if (Vertices[Index].DepthIndex == IndexNone) { StrongConnect(Index, StrongConnect); } } VstProject.AddMapping(&*AstProject); return Move(AstProject); } private: TSRef DesugarPackage(const Vst::Package& VstPackage) { // Turn the language version override into an effective version. uint32_t EffectiveVerseVersion = VstPackage._VerseVersion.Get(Verse::Version::Default); if (EffectiveVerseVersion < Verse::Version::Minimum || EffectiveVerseVersion > Verse::Version::Maximum) { AppendGlitch( &VstPackage, EDiagnostic::ErrSystem_InvalidVerseVersion, CUTF8String("Invalid Verse version for package %s: %u", VstPackage._Name.AsCString(), EffectiveVerseVersion)); } TSRef AstPackage = TSRef::New( VstPackage._Name, VstPackage._VersePath, VstPackage._VerseScope, VstPackage._Role, EffectiveVerseVersion, VstPackage._UploadedAtFNVersion, VstPackage._VniDestDir.IsSet(), VstPackage._bTreatModulesAsImplicit, VstPackage._bAllowExperimental); TGuardValue CurrentPackageGuard(_Package, AstPackage.Get()); // Desugar all the package's modules or snippets. for (const TSRef& VstNode : VstPackage.GetChildren()) { if (VstNode->GetElementType() == Vst::NodeType::Module) { AstPackage->AppendMember(DesugarModule(static_cast(*VstNode))); } else if (VstNode->GetElementType() == Vst::NodeType::Snippet) { AstPackage->AppendMember(DesugarSnippet(static_cast(*VstNode))); } else { ULANG_ERRORF("Toolchain must ensure that a package only ever contains modules or snippets."); } } VstPackage.AddMapping(&*AstPackage); return Move(AstPackage); } TSRef DesugarModule(const Vst::Module& VstModule) { TSRef AstModule = TSRef::New(VstModule._Name); // Is a vmodule file present? if (VstModule._FilePath.ToStringView().EndsWith(".vmodule")) { // Yes - mark public to mimic legacy behavior of vmodule files AstModule->_bLegacyPublic = true; } // Desugar the module's children, which may be either submodules or snippets. for (const TSRef& VstNode : VstModule.GetChildren()) { if (VstNode->GetElementType() == Vst::NodeType::Module) { AstModule->AppendMember(DesugarModule(static_cast(*VstNode))); } else if (VstNode->GetElementType() == Vst::NodeType::Snippet) { AstModule->AppendMember(DesugarSnippet(static_cast(*VstNode))); } else { ULANG_ENSUREF(false, "Toolchain must ensure that a module only ever contains modules or snippets."); } } VstModule.AddMapping(&*AstModule); return Move(AstModule); } TSRef DesugarSnippet(const Vst::Snippet& VstSnippet) { TSRef AstSnippet = TSRef::New(VstSnippet._Path); // Desugar all the snippet's top-level expressions. for (const TSRef& VstNode : VstSnippet.GetChildren()) { if (!VstNode->IsA()) { AstSnippet->AppendMember(DesugarExpressionVst(*VstNode)); } } VstSnippet.AddMapping(&*AstSnippet); return Move(AstSnippet); } TSRef DesugarClauseAsExpression(const Vst::Node& MaybeClauseVst) { if (MaybeClauseVst.GetElementType() != Verse::Vst::NodeType::Clause) { // If the expression isn't a clause, just desugar it directly. return DesugarExpressionVst(MaybeClauseVst); } else { const Verse::Vst::Clause& ClauseVst = MaybeClauseVst.As(); // Determine if the clause has a single non-comment child expression. const Vst::Node* NonCommentChild = nullptr; for (const Vst::Node* ChildVst : ClauseVst.GetChildren()) { if (!ChildVst->IsA()) { if (NonCommentChild == nullptr) { NonCommentChild = ChildVst; } else { NonCommentChild = nullptr; break; } } } if (NonCommentChild) { // If so, desugar that expression as though it occurred on its own. return DesugarExpressionVst(*NonCommentChild); } else { // Otherwise, desugar the clause as a code block. return DesugarClauseAsCodeBlock(ClauseVst); } } } TSRef DesugarWhere(const Vst::Where& WhereVst) { TSRef LhsAst = DesugarExpressionVst(*WhereVst.GetLhs()); Vst::Where::RhsView RhsVstView = WhereVst.GetRhs(); TSPtrArray RhsAst; RhsAst.Reserve(RhsVstView.Num()); for (const TSRef& RhsVst : RhsVstView) { RhsAst.Add(DesugarExpressionVst(*RhsVst)); } return AddMapping(WhereVst, TSRef::New(Move(LhsAst), Move(RhsAst))); } TSRef DesugarMutation(const Vst::Mutation& MutationVst) { switch (MutationVst._Keyword) { case Vst::Mutation::EKeyword::Var: return AddMapping(MutationVst, TSRef::New(DesugarExpressionVst(*MutationVst.Child()))); case Vst::Mutation::EKeyword::Set: return AddMapping(MutationVst, TSRef::New(DesugarExpressionVst(*MutationVst.Child()))); default: ULANG_UNREACHABLE(); } } // This is old code that can't handle named parameters. // Only kept to compile code published before 20.30, since this code ignores some errors that the new code complains about. struct SNameTypeIdentifierPair { const Vst::Identifier* Name; const Vst::Identifier* Type; }; TSRef DesugarLocalizableOld( const Vst::Definition& DefinitionVst, const Vst::Identifier& MessageKeyVst, const CUTF8String& MessageDefaultText, const TSRef& MessageTypeVst, const TArray& NameTypePairs, bool bIsFunction) { const CSymbol MessageKeySymbol = VerifyAddSymbol(&MessageKeyVst, MessageKeyVst._OriginalCode); TArray> MapClauseExprs; const CSymbol MakeLocalizableSymbol = _Symbols.AddChecked("MakeLocalizableValue"); TSRef MakeLocalizableIdentifier = TSRef::New(MakeLocalizableSymbol); MakeLocalizableIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst); // ** special exception here to allow looking up this identifier which is to // another module, we're doing this as a short term protection against users writing // code that depends on internal details of the message type MakeLocalizableIdentifier->GrantUnrestrictedAccess(); auto CreateArgDefinition = [this, &MapClauseExprs, &DefinitionVst, &MakeLocalizableIdentifier](const SNameTypeIdentifierPair& NameTypePair) -> TSRef { const CSymbol NameSymbol = VerifyAddSymbol(NameTypePair.Name, NameTypePair.Name->_OriginalCode); const CSymbol TypeSymbol = VerifyAddSymbol(NameTypePair.Type, NameTypePair.Type->_OriginalCode); TSRef NameIdentifier = AddMapping(*NameTypePair.Name, TSRef::New(NameSymbol)); TSRef TypeIdentifier = AddMapping(*NameTypePair.Type, TSRef::New(TypeSymbol)); // create the function parameter definition TSRef ArgDefinition = TSRef::New( NameIdentifier, TypeIdentifier, nullptr); ArgDefinition->SetNonReciprocalMappedVstNode(&DefinitionVst); // create an invocation passing the argument to MakeLocalizableValue // so it can be added to the Substitutions map TSRef MakeLocalizableValueInvocation = TSRef::New( CExprInvocation::EBracketingStyle::Parentheses, MakeLocalizableIdentifier, NameIdentifier); MakeLocalizableValueInvocation->SetNonReciprocalMappedVstNode(&DefinitionVst); TSRef ArgNameString = TSRef::New(NameTypePair.Name->_OriginalCode); ArgNameString->SetNonReciprocalMappedVstNode(NameTypePair.Name); TSRef MapClauseExpr = TSRef::New( Move(ArgNameString), MakeLocalizableValueInvocation); MapClauseExpr->SetNonReciprocalMappedVstNode(&DefinitionVst); MapClauseExprs.Add(Move(MapClauseExpr)); return ArgDefinition; }; // in the function case, we have to differentiate between a single // parameter function and a multi-parameter function where for // multi-parameter functions the AST is expected to have the // list of definitions wrapped by a CExprMakeTuple node, but // single parameter functions should not be wrapped TSPtr ElementArguments; if (NameTypePairs.Num() == 1) { ElementArguments = CreateArgDefinition(NameTypePairs[0]); } else { // multi-parameter functions need to wrap the // definitions in a CExprMakeTuple node TSRef ElementArgumentsTuple = TSRef::New(); ElementArgumentsTuple->SetNonReciprocalMappedVstNode(&DefinitionVst); for (const SNameTypeIdentifierPair& NameTypePair : NameTypePairs) { ElementArgumentsTuple->AppendSubExpr(CreateArgDefinition(NameTypePair)); } ElementArguments = Move(ElementArgumentsTuple); } TSRefArray ArgumentExprs; { // Key argument // for the function case, the current scope will include the function name, so we pass the null symbol here TSRef KeyPath = TSRef::New(bIsFunction ? CSymbol() : MessageKeySymbol); KeyPath->SetNonReciprocalMappedVstNode(&MessageKeyVst); ArgumentExprs.Add(Move(KeyPath)); } { // DefaultText argument TSRef DefaultTextString = TSRef::New(MessageDefaultText); ArgumentExprs.Add(Move(DefaultTextString)); } { // Substitutions argument const CSymbol MapSymbol = _Symbols.AddChecked("map"); TSRef MapIdentifier = TSRef::New(MapSymbol); MapIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst); TSRef MapMacroExpr = TSRef::New(MapIdentifier); MapMacroExpr->SetNonReciprocalMappedVstNode(&DefinitionVst); MapMacroExpr->AppendClause(CExprMacroCall::CClause(EMacroClauseTag::None, Verse::Vst::Clause::EForm::Synthetic, Move(MapClauseExprs))); ArgumentExprs.Add(Move(MapMacroExpr)); } TSRef ArgTuple = WrapExpressionListInTuple(Move(ArgumentExprs), DefinitionVst, false); const CSymbol MakeMessageSymbol = _Symbols.AddChecked("MakeMessageInternal"); TSRef MakeMessageIdentifier = TSRef::New(MakeMessageSymbol); // ** special exception here to allow looking up this identifier which is to // another module, we're doing this as a short term protection against users writing // code that depends on internal details of the message type MakeMessageIdentifier->GrantUnrestrictedAccess(); MakeMessageIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst); TSRef MakeMessageInvocation = TSRef::New( CExprInvocation::EBracketingStyle::Parentheses, Move(MakeMessageIdentifier), Move(ArgTuple)); MakeMessageInvocation->SetNonReciprocalMappedVstNode(&DefinitionVst); // We explicitly desugar the identifier because the localizable definition may have an explicit qualifer. // i.e. `(super_class:)MyMessage:message="B"` TSRef MessageKeyIdentifier = DesugarIdentifier(MessageKeyVst); if (MessageKeyVst.HasAttributes()) { MessageKeyIdentifier->_Attributes = DesugarAttributes(MessageKeyVst.GetAux()->GetChildren()); } TSPtr DefinitionElement; if (bIsFunction) { TSRef ElementInvocation = TSRef::New( CExprInvocation::EBracketingStyle::Parentheses, Move(MessageKeyIdentifier), Move(ElementArguments.AsRef())); ElementInvocation->SetNonReciprocalMappedVstNode(&DefinitionVst); DefinitionElement = ElementInvocation; } else { DefinitionElement = MessageKeyIdentifier; } TSRef Definition = TSRef::New(DefinitionElement.AsRef(), DesugarExpressionVst(*MessageTypeVst), Move(MakeMessageInvocation)); return Definition; } // This is the new code that improves localization, see SOL-6057 void FillinClauseExprs( const Vst::Definition& DefinitionVst, TArray>& MapClauseExprs, const TSPtrArray& Parameters) { const CSymbol MakeLocalizableSymbol = _Symbols.AddChecked("MakeLocalizableValue"); TSRef MakeLocalizableIdentifier = TSRef::New(MakeLocalizableSymbol); MakeLocalizableIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst); // ** special exception here to allow looking up this identifier which is to // another module, we're doing this as a short term protection against users writing // code that depends on internal details of the message type MakeLocalizableIdentifier->GrantUnrestrictedAccess(); for (const TSPtr& Parameter : Parameters) { if (Parameter->GetNodeType() == EAstNodeType::Definition) { TSPtr ParamDefinition = Parameter.As(); TSPtr Element = ParamDefinition->Element(); if (!Element || Element->GetNodeType() != EAstNodeType::Identifier_Unresolved) { continue; } TSPtr ParamUnresolved = Element.As(); CSymbol NameSymbol = ParamUnresolved->_Symbol; const Vst::Node* NameVstNode = ParamUnresolved->GetMappedVstNode(); TSRef NameIdentifier = TSRef::New(NameSymbol); NameIdentifier->SetNonReciprocalMappedVstNode(ParamUnresolved->GetMappedVstNode()); // create an invocation passing the argument to MakeLocalizableValue // so it can be added to the Substitutions map TSRef MakeLocalizableValueInvocation = TSRef::New( CExprInvocation::EBracketingStyle::Parentheses, MakeLocalizableIdentifier, NameIdentifier); MakeLocalizableValueInvocation->SetNonReciprocalMappedVstNode(NameVstNode); TSRef ArgNameString = TSRef::New(NameSymbol.AsString()); ArgNameString->SetNonReciprocalMappedVstNode(NameVstNode); TSRef MapClauseExpr = TSRef::New( Move(ArgNameString), MakeLocalizableValueInvocation); MapClauseExpr->SetNonReciprocalMappedVstNode(&DefinitionVst); MapClauseExprs.Add(Move(MapClauseExpr)); } else if (Parameter->GetNodeType() == EAstNodeType::Invoke_MakeTuple) { TSPtr ParamTuple = Parameter.As(); FillinClauseExprs(DefinitionVst, MapClauseExprs, ParamTuple->GetSubExprs()); } } } TSRef DesugarLocalizable( const Vst::Definition& DefinitionVst, const Vst::Identifier& MessageKeyVst, const CUTF8String& MessageDefaultText, const TSRef& MessageTypeVst, TSPtrArray& Parameters, bool bIsFunction) { const CSymbol MessageKeySymbol = VerifyAddSymbol(&MessageKeyVst, MessageKeyVst._OriginalCode); TArray> MapClauseExprs; FillinClauseExprs(DefinitionVst, MapClauseExprs, Parameters); TSPtr ElementParameters; if (Parameters.Num() == 1) { ElementParameters = Parameters[0]; } else { // multi-parameter functions need to wrap the // definitions in a CExprMakeTuple node TSRef ElementParametersTuple = TSRef::New(); ElementParametersTuple->SetNonReciprocalMappedVstNode(&DefinitionVst); for (const TSPtr& Parameter : Parameters) { ElementParametersTuple->AppendSubExpr(Parameter); } ElementParameters = Move(ElementParametersTuple); } TSRefArray ArgumentExprs; { // Key argument // for the function case, the current scope will include the function name, so we pass the null symbol here TSRef KeyPath = TSRef::New(bIsFunction ? CSymbol() : MessageKeySymbol); KeyPath->SetNonReciprocalMappedVstNode(&MessageKeyVst); ArgumentExprs.Add(Move(KeyPath)); } { // DefaultText argument TSRef DefaultTextString = TSRef::New(MessageDefaultText); DefaultTextString->SetNonReciprocalMappedVstNode(&DefinitionVst); ArgumentExprs.Add(Move(DefaultTextString)); } { // Substitutions argument const CSymbol MapSymbol = _Symbols.AddChecked("map"); TSRef MapIdentifier = TSRef::New(MapSymbol); MapIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst); TSRef MapMacroExpr = TSRef::New(MapIdentifier); MapMacroExpr->SetNonReciprocalMappedVstNode(&DefinitionVst); MapMacroExpr->AppendClause(CExprMacroCall::CClause(EMacroClauseTag::None, Verse::Vst::Clause::EForm::Synthetic, Move(MapClauseExprs))); ArgumentExprs.Add(Move(MapMacroExpr)); } TSRef ArgTuple = WrapExpressionListInTuple(Move(ArgumentExprs), DefinitionVst, false); const CSymbol MakeMessageSymbol = _Symbols.AddChecked("MakeMessageInternal"); TSRef MakeMessageIdentifier = TSRef::New(MakeMessageSymbol); // ** special exception here to allow looking up this identifier which is to // another module, we're doing this as a short term protection against users writing // code that depends on internal details of the message type MakeMessageIdentifier->GrantUnrestrictedAccess(); MakeMessageIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst); TSRef MakeMessageInvocation = TSRef::New( CExprInvocation::EBracketingStyle::Parentheses, Move(MakeMessageIdentifier), Move(ArgTuple)); MakeMessageInvocation->SetNonReciprocalMappedVstNode(&DefinitionVst); TSRef MessageKeyIdentifier = AddMapping(MessageKeyVst, TSRef::New(MessageKeySymbol)); if (MessageKeyVst.HasAttributes()) { MessageKeyIdentifier->_Attributes = DesugarAttributes(MessageKeyVst.GetAux()->GetChildren()); } TSPtr DefinitionElement; if (bIsFunction) { TSRef ElementInvocation = TSRef::New( CExprInvocation::EBracketingStyle::Parentheses, Move(MessageKeyIdentifier), Move(ElementParameters.AsRef())); ElementInvocation->SetNonReciprocalMappedVstNode(&DefinitionVst); DefinitionElement = ElementInvocation; } else { DefinitionElement = MessageKeyIdentifier; } TSRef Definition = TSRef::New(DefinitionElement.AsRef(), DesugarExpressionVst(*MessageTypeVst), Move(MakeMessageInvocation)); return Definition; } // Common code for new and old code for localize. // Selects new or old behaviour depending on UploadedAtFNVesrsion. TSPtr TryDesugarLocalizable(const Vst::Definition& DefinitionVst) { TSRef LhsVst = DefinitionVst.GetOperandLeft(); bool bEnableNamedParametersForLocalize = VerseFN::UploadedAtFNVersion::EnableNamedParametersForLocalize(_Package->_UploadedAtFNVersion); // // there are several valid forms for localized definitions // this list is mirrored in Localization.versetest // // 1) TheMsg := "The Message" // 2) TheMsg : message = "The Message" // 3) TheMsg(Name:string) := "The Message" // 4) TheMsg(Name:string) : message = "The Message" // 5) TheMsg(Name:string) := "The Message to {Name}" // 6) TheMsg(Name:string) : message = "The Message to {Name}" // // NOTE that currently we do not support any of the forms(1, 3, 5) that omit the type name, // but we still parse every form in order to give better error messages here // const Vst::Identifier* MaybeLocalizedIdentifier = nullptr; TSPtr MaybeLocalizedType; const Vst::Clause* MaybeLocalizedArgs = nullptr; if (const Vst::Identifier* LhsIdentifier = LhsVst->AsNullable()) { // this is only hit for case 1 where the identifier is the Lhs of the definition MaybeLocalizedIdentifier = LhsIdentifier; } else if (const Vst::TypeSpec* LhsTypeSpec = LhsVst->AsNullable()) { // this is hit for case 2, 4, and 6, where the user explicitly stated a type if (LhsTypeSpec->HasLhs()) { const Vst::Node& LhsNode = *LhsTypeSpec->GetLhs(); // case 2? MaybeLocalizedIdentifier = LhsNode.AsNullable(); if (!MaybeLocalizedIdentifier) { const Vst::PrePostCall* PrePostCallNode = LhsNode.AsNullable(); if (PrePostCallNode && (PrePostCallNode->GetChildCount() >= 2)) { TSRef PrePostCallFirstChild = PrePostCallNode->GetChildren()[0]; TSRef PrePostCallSecondChild = PrePostCallNode->GetChildren()[1]; // this is case 4 and 6 MaybeLocalizedIdentifier = PrePostCallFirstChild->AsNullable(); MaybeLocalizedArgs = PrePostCallSecondChild->AsNullable(); } } } MaybeLocalizedType = LhsTypeSpec->GetRhs(); } else if (const Vst::PrePostCall* PrePostCallNode = LhsVst->AsNullable()) { // this is hit for case 3 and 5 if (PrePostCallNode->GetChildCount() >= 2) { TSRef PrePostCallFirstChild = PrePostCallNode->GetChildren()[0]; TSRef PrePostCallSecondChild = PrePostCallNode->GetChildren()[1]; // this is case 3 and 5 MaybeLocalizedIdentifier = PrePostCallFirstChild->AsNullable(); MaybeLocalizedArgs = PrePostCallSecondChild->AsNullable(); } } if (MaybeLocalizedIdentifier != nullptr) { TArray LocalizedArgumentNameTypePairs; if (!bEnableNamedParametersForLocalize) { if (MaybeLocalizedArgs) { // this collects the pairs of function parameter name and type, (Subject,string) and (Rank,int) in the above example for (const TSRef& ArgNode : MaybeLocalizedArgs->GetChildren()) { if (ArgNode->GetElementType() == Vst::NodeType::TypeSpec) { const Vst::TypeSpec& ArgTypeSpecNode = ArgNode->As(); if (ArgTypeSpecNode.HasLhs()) { const Vst::Identifier* ArgNameIdentifier = ArgTypeSpecNode.GetLhs()->AsNullable(); const Vst::Identifier* ArgTypeIdentifier = ArgTypeSpecNode.GetRhs()->AsNullable(); if (ArgNameIdentifier && ArgTypeIdentifier) { LocalizedArgumentNameTypePairs.Add(SNameTypeIdentifierPair{ ArgNameIdentifier, ArgTypeIdentifier }); } } } } } } // does this identifier have a 'localizes' attribute attached? if (MaybeLocalizedIdentifier->IsAttributePresent("localizes")) { // first ensure that they've specified a type (we don't currently support omitting the type) if (MaybeLocalizedType == nullptr) { AppendGlitch(&DefinitionVst, EDiagnostic::ErrSemantic_LocalizesMustSpecifyType); return AddMapping(DefinitionVst, TSRef::New()); } TSPtrArray Arguments; if (bEnableNamedParametersForLocalize) { if (MaybeLocalizedArgs) { for (const TSRef& ParamVst : MaybeLocalizedArgs->GetChildren()) { Arguments.Add(DesugarExpressionVst(*ParamVst)); } } } // Now get the RHS value TSRef RhsVst = DefinitionVst.GetOperandRight(); TSPtr ValueNode = RhsVst; // Unwrap if wrapped in a clause if (RhsVst->GetElementType() == Vst::NodeType::Clause) { if (RhsVst->GetChildCount() == 1) { ValueNode = RhsVst->GetChildren()[0]; } else { // Bad clause - too many children ValueNode.Reset(); } } // Only support the Rhs being a string literal or an interpolated string expression const Vst::Identifier* MessageKeyVst = nullptr; CUTF8String MessageDefaultText; bool bIsExternal = false; if (ValueNode) { if (const Vst::StringLiteral* RhsStringLiteral = ValueNode->AsNullable()) { MessageKeyVst = MaybeLocalizedIdentifier; MessageDefaultText = RhsStringLiteral->GetSourceText(); } else if (const Vst::InterpolatedString* RhsInterpolatedString = ValueNode->AsNullable()) { bool bHasNonLiteralInterpolants = false; CUTF8StringBuilder DecodedString; for (const TSRef& RhsChildNode : RhsInterpolatedString->GetChildren()) { auto AppendInvalidInterpolantError = [&]() { AppendGlitch(RhsChildNode, EDiagnostic::ErrSemantic_LocalizesEscape, "Localized message strings may only contain string and character literals, and interpolated arguments."); }; if (Vst::StringLiteral* StringLiteral = RhsChildNode->AsNullable()) { DecodedString.Append(StringLiteral->GetSourceText()); } else if (Vst::Interpolant* Interpolant = RhsChildNode->AsNullable()) { const Vst::Clause& InterpolantArgClause = Interpolant->GetChildren()[0]->As(); TSRefArray DesugaredInterpolantArgs = DesugarExpressionList(InterpolantArgClause.GetChildren()); if (DesugaredInterpolantArgs.Num() == 0) { // Ignore interpolants that contained no syntax other than whitespace or comment trivia. } else if (DesugaredInterpolantArgs.Num() == 1) { TSRef InterpolantArg = DesugaredInterpolantArgs[0]; if (CExprChar* Char = AsNullable(InterpolantArg)) { DecodedString.Append(Char->AsString()); } else if (CExprIdentifierUnresolved* Identifier = AsNullable(InterpolantArg)) { DecodedString.Append('{'); if (Identifier->Qualifier() || Identifier->Context()) { AppendGlitch(InterpolantArg->GetMappedVstNode(), EDiagnostic::ErrSemantic_LocalizesEscape, "Localized message string interpolated arguments must not be qualified."); } // Note: this does not verify that the identifier is an argument to the function. DecodedString.Append(Identifier->_Symbol.AsStringView()); DecodedString.Append('}'); bHasNonLiteralInterpolants = true; } else { AppendInvalidInterpolantError(); } } else { AppendInvalidInterpolantError(); } } else { AppendInvalidInterpolantError(); } } if (MaybeLocalizedArgs || !bHasNonLiteralInterpolants) { MessageKeyVst = MaybeLocalizedIdentifier; MessageDefaultText = DecodedString.MoveToString(); } } else if (const Vst::Macro* RhsMacro = ValueNode->AsNullable()) { const Vst::Node* RhsMacroName = RhsMacro->GetName(); if (RhsMacroName) { const Vst::Identifier* RhsMacroNameIdentifier = RhsMacroName->AsNullable(); if (RhsMacroNameIdentifier) { bIsExternal = RhsMacroNameIdentifier->GetSourceText() == "external"; } } } } if (MessageKeyVst != nullptr && MaybeLocalizedType != nullptr) { // the success case is here - we gathered the message key, default text, and any function arguments const bool bIsFunction = (MaybeLocalizedArgs != nullptr); if (bEnableNamedParametersForLocalize) { return AddMapping(DefinitionVst, DesugarLocalizable(DefinitionVst, *MessageKeyVst, MessageDefaultText, MaybeLocalizedType.AsRef(), Arguments, bIsFunction)); } else { return AddMapping(DefinitionVst, DesugarLocalizableOld(DefinitionVst, *MessageKeyVst, MessageDefaultText, MaybeLocalizedType.AsRef(), LocalizedArgumentNameTypePairs, bIsFunction)); } } else if (bIsExternal) { // silently allow this through, no need to desugar } else { AppendGlitch(&DefinitionVst, EDiagnostic::ErrSemantic_LocalizesRhsMustBeString); return AddMapping(DefinitionVst, TSRef::New()); } } } return nullptr; } TSRef DesugarDefinition(const Vst::Definition& DefinitionVst) { TSRef LhsVst = DefinitionVst.GetOperandLeft(); TSRef RhsVst = DefinitionVst.GetOperandRight(); TSPtr Element; TSPtr ValueDomain; CSymbol Name; TSPtr LocalizableDefinition = TryDesugarLocalizable(DefinitionVst); if (LocalizableDefinition != nullptr) { return LocalizableDefinition.AsRef(); } if (const Vst::TypeSpec* LhsTypeSpec = LhsVst->AsNullable()) { if (LhsTypeSpec->HasLhs()) { // Definition is `x:t = y` Element = DesugarMaybeNamed(*LhsTypeSpec->GetLhs(), Name); } ValueDomain = DesugarExpressionVst(*LhsTypeSpec->GetRhs()); } else { // Definition is `x := y` Element = DesugarMaybeNamed(*LhsVst, Name); } TSRef Value = DesugarClauseAsExpression(*RhsVst); if (!Name.IsNull() && !ValueDomain) { // Looks like a named argument - matched `_Parameter` will be set later in semantic analysis return AddMapping(DefinitionVst, TSRef::New(Name, Move(Element), Move(Value))); } TSRef DefinitionAst = TSRef::New(Move(Element), Move(ValueDomain), Move(Value)); if (!Name.IsNull()) { // Looks like a named parameter DefinitionAst->SetName(Name); } return AddMapping(DefinitionVst, Move(DefinitionAst)); } TSRef DesugarAssignment(const Vst::Assignment& AssignmentVst) { TSRef LhsVst = AssignmentVst.GetOperandLeft(); TSRef RhsVst = AssignmentVst.GetOperandRight(); Vst::Assignment::EOp Op = RhsVst->GetTag(); // Desugar the LHS and RHS subexpressions. TSRef LhsAst = DesugarExpressionVst(*LhsVst); TSRef RhsAst = DesugarClauseAsExpression(*RhsVst); return AddMapping(AssignmentVst, TSRef::New(Op, Move(LhsAst), Move(RhsAst))); } TSRef DesugarBinaryOpLogicalAndOr(const Vst::Node& VstNode) { const Vst::NodeType ThisNodeType = VstNode.GetElementType(); const bool bIsLogicalOr = ThisNodeType == Vst::NodeType::BinaryOpLogicalOr; const bool bIsLogicalAnd = ThisNodeType == Vst::NodeType::BinaryOpLogicalAnd; const int32_t NumChildren = VstNode.GetChildCount(); if (NumChildren == 0) { AppendGlitch(&VstNode, EDiagnostic::ErrSemantic_BinaryOpNoOperands); return AddMapping(VstNode, TSRef::New(nullptr, /*bCanFail=*/true)); } // Convert the flat operand list into a right-recursive binary tree: (0 (1 (2 3))) // Start with the rightmost child const Vst::Node* RHSNode = VstNode.GetChildren().Last().Get(); TSRef Result = DesugarExpressionVst(*RHSNode); // Then loop back and build expression tree for (int i = NumChildren - 2; i >= 0; --i) { // Evaluate LHS expression const Vst::Node* LHSNode = VstNode.GetChildren()[i].Get(); TSRef LHS = DesugarExpressionVst(*LHSNode); // Build expression node if (bIsLogicalAnd) { Result = TSRef::New(Move(LHS), Move(Result)); } else if (bIsLogicalOr) { Result = TSRef::New(Move(LHS), Move(Result)); } else { ULANG_UNREACHABLE(); } } // RHS contains the final expression tree for this node return AddMapping(VstNode, Move(Result)); } TSRef DesugarPrefixOpLogicalNot(const Vst::PrefixOpLogicalNot& PrefixOpLogicalNotNode) { if (PrefixOpLogicalNotNode.GetChildCount() == 0) { AppendGlitch(&PrefixOpLogicalNotNode, EDiagnostic::ErrSemantic_PrefixOpNoOperand); return AddMapping(PrefixOpLogicalNotNode, TSRef::New(nullptr, /*bCanFail=*/true)); } else { const Vst::Node* OperandVst = PrefixOpLogicalNotNode.GetChildren()[0]; TSRef OperandAst = DesugarExpressionVst(*OperandVst); return AddMapping(PrefixOpLogicalNotNode, TSRef::New(Move(OperandAst))); } } TSRef DesugarBinaryOpCompare(const Vst::BinaryOpCompare& BinaryOpCompareNode) { const int32_t NumChildren = BinaryOpCompareNode.GetChildCount(); if (NumChildren != 2) { AppendGlitch(&BinaryOpCompareNode, EDiagnostic::ErrSemantic_BinaryOpExpectedTwoOperands); return AddMapping(BinaryOpCompareNode, TSRef::New(nullptr, /*bCanFail=*/true)); } else { const Vst::Node* LhsNode = BinaryOpCompareNode.GetChildren()[0].Get(); TSRef Lhs = DesugarExpressionVst(*LhsNode); // Get RHS operand const Vst::Node* RhsNode = BinaryOpCompareNode.GetChildren()[1].Get(); TSRef Rhs = DesugarExpressionVst(*RhsNode); TSRef Argument = TSRef::New(Move(Lhs), Move(Rhs)); Argument->SetNonReciprocalMappedVstNode(&BinaryOpCompareNode); TSRef Result = AddMapping(BinaryOpCompareNode, TSRef::New( RhsNode->GetTag(), Argument)); return Result; } } TSRef DesugarBinaryOp(const Vst::BinaryOp& BinaryOpNode) { using EOp = Vst::BinaryOp::op; const int32_t NumChildren = BinaryOpNode.GetChildCount(); if (NumChildren == 0) { AppendGlitch(&BinaryOpNode, EDiagnostic::ErrSemantic_BinaryOpNoOperands); return AddMapping(BinaryOpNode, TSRef::New()); } // Get our first LHS operand const Vst::Node* LHSNode = BinaryOpNode.GetChildren()[0].Get(); TSPtr LhsPtr; bool bHasLeadingOperator = false; if (LHSNode->GetElementType() == Vst::NodeType::Operator && BinaryOpNode.GetChildCount() > 1) { bHasLeadingOperator = true; const Vst::Node* OperatorNode = BinaryOpNode.GetChildren()[0].Get(); const Vst::Node* OprandNode = BinaryOpNode.GetChildren()[1].Get(); const CUTF8String& OpString = OperatorNode->As().GetSourceText(); TSRef Result = DesugarExpressionVst(*OprandNode); if (OpString[0] == u'-') { Result = TSRef::New(CExprUnaryArithmetic::EOp::Negate, Move(Result)); LhsPtr = AddMapping(BinaryOpNode, Move(Result)); } else { LhsPtr = Move(Result); } } else { // Get our first LHS operand LhsPtr = DesugarExpressionVst(*LHSNode); } TSRef Lhs = LhsPtr.AsRef(); auto HandleMalformedVst = [&Lhs](TSRef&& Rhs) { TSRef ErrorExpr = TSRef::New(); ErrorExpr->AppendChild(Move(Lhs)); ErrorExpr->AppendChild(Move(Rhs)); Lhs = Move(ErrorExpr); }; // Then loop and build expression tree for (int i = bHasLeadingOperator ? 2 : 1; i < NumChildren; i += 2) { const Vst::Node* OperatorNode = BinaryOpNode.GetChildren()[i].Get(); ULANG_ENSUREF(OperatorNode->GetTag() == EOp::Operator, "Malformed binary op node, expecting an operator. "); if (ULANG_ENSUREF(i + 1 < NumChildren, "Malformed binary Op node, no trailing operand.")) { const Vst::Node* RhsOperandNode = BinaryOpNode.GetChildren()[i + 1].Get(); ULANG_ENSUREF(RhsOperandNode->GetTag() == EOp::Operand, "Malformed binary op node, expecting an operand."); TSRef Rhs = DesugarExpressionVst(*RhsOperandNode); if (OperatorNode->GetElementType() == Vst::NodeType::Operator) { const CUTF8String& OpString = OperatorNode->As().GetSourceText(); if (OpString.ByteLen() == 1) { CExprBinaryArithmetic::EOp ArithmeticOp; if(BinaryOpNode.GetElementType() == Vst::NodeType::BinaryOpAddSub) ArithmeticOp = (OpString[0] == u'+') ? CExprBinaryArithmetic::EOp::Add : CExprBinaryArithmetic::EOp::Sub; else if(BinaryOpNode.GetElementType() == Vst::NodeType::BinaryOpMulDivInfix) ArithmeticOp = (OpString[0] == u'*') ? CExprBinaryArithmetic::EOp::Mul : CExprBinaryArithmetic::EOp::Div; else ULANG_UNREACHABLE(); TSRef Argument = TSRef::New(Move(Lhs), Move(Rhs)); Argument->SetNonReciprocalMappedVstNode(OperatorNode); Lhs = AddMapping(*OperatorNode, TSRef::New( ArithmeticOp, Argument)); } else { HandleMalformedVst(Move(Rhs)); } } else if (OperatorNode->GetElementType() == Vst::NodeType::Identifier) { TSRef Argument = TSRef::New(Move(Lhs), Move(Rhs)); Argument->SetNonReciprocalMappedVstNode(OperatorNode); TSRef Invocation = TSRef::New(Argument); const CSymbol OperatorSymbol = VerifyAddSymbol(OperatorNode, CUTF8String("operator'%s'", OperatorNode->As().GetSourceCStr())); Invocation->SetCallee(TSRef::New(OperatorSymbol)); Lhs = AddMapping(*OperatorNode, Move(Invocation)); } else { HandleMalformedVst(Move(Rhs)); } } } // LHS contains the final expression tree for this node return Move(Lhs); } TSRef DesugarBinaryOpRange(const Vst::BinaryOpRange& BinaryOpRange) { TSRef Lhs = DesugarExpressionVst(*BinaryOpRange.GetChildren()[0]); TSRef Rhs = DesugarExpressionVst(*BinaryOpRange.GetChildren()[1]); return AddMapping(BinaryOpRange, TSRef::New(Move(Lhs), Move(Rhs))); } TSRef DesugarBinaryOpArrow(const Vst::BinaryOpArrow& BinaryOpArrow) { TSRef Lhs = DesugarExpressionVst(*BinaryOpArrow.GetChildren()[0]); TSRef Rhs = DesugarExpressionVst(*BinaryOpArrow.GetChildren()[1]); return AddMapping(BinaryOpArrow, TSRef::New(Move(Lhs), Move(Rhs))); } TSRef DesugarMaybeNamed(Vst::Node& VstNode, CSymbol& Name) { if ((VstNode.GetElementType() == Vst::NodeType::PrePostCall) && (VstNode.GetChildCount() >= 2)) { TSRef VarChild0 = VstNode.GetChildren()[0]; Vst::Node* VarChild1 = VstNode.GetChildren()[1]; // No qualified named parameters yet if ((VarChild0->GetTag() == Vst::PrePostCall::Op::Option) && (VarChild0->GetElementType() == Vst::NodeType::Clause) && (VarChild1->GetTag() == Vst::PrePostCall::Op::Expression) && (VarChild1->GetElementType() == Vst::NodeType::Identifier)) { const Vst::Identifier& Identifier = VarChild1->As(); if (Identifier.IsQualified()) { AppendGlitch( Identifier.GetQualification(), EDiagnostic::ErrSemantic_Unsupported, "Qualifiers are not yet supported on named parameters."); } Name = VerifyAddSymbol(VarChild1, Identifier.GetSourceText()); // Temporarily remove option clause so option not created and track as explicitly ?named parameter or argument VstNode.AccessChildren().RemoveAt(0); // Continue processing TSRef NamedExpr = DesugarExpressionVst(VstNode); // Replace temporarily removed option clause so VST remains as it was originally VstNode.AccessChildren().Insert(VarChild0, 0); return NamedExpr; } } return DesugarExpressionVst(VstNode); } TSRef DesugarTypeSpec(const Vst::TypeSpec& TypeSpecVst) { TSPtr Lhs = nullptr; CSymbol Name; if (TypeSpecVst.HasLhs()) { Lhs = DesugarMaybeNamed(*TypeSpecVst.GetLhs(), Name); } TSRef Rhs = DesugarExpressionVst(*TypeSpecVst.GetRhs()); TSPtr NoDefaultValue = nullptr; // Create a CExprDefinition AST node. TSRef DefinitionAst = TSRef::New(Move(Lhs), Move(Rhs), Move(NoDefaultValue)); if (!Name.IsNull()) { DefinitionAst->SetName(Name); } // Desugar the type-spec's attributes. if (TypeSpecVst.HasAttributes()) { DefinitionAst->_Attributes = DesugarAttributes(TypeSpecVst.GetAux()->GetChildren()); } return AddMapping(TypeSpecVst, Move(DefinitionAst)); } TSRef DesugarCall(bool bCalledWithBrackets, const Vst::Clause& CallArgs, TSRef&& Callee) { // Create an invocation AST node. return AddMapping(CallArgs, TSRef::New( bCalledWithBrackets ? CExprInvocation::EBracketingStyle::SquareBrackets : CExprInvocation::EBracketingStyle::Parentheses, Move(Callee), DesugarExpressionListAsExpression(CallArgs, CallArgs.GetForm(), false))); } TSRef DesugarPrePostCall(const Vst::PrePostCall& Ppc) { using PpcOp = Vst::PrePostCall::Op; const int32_t NumPpcNodes = Ppc.GetChildCount(); const int32_t ExpressionIndex = [&]() { for (int32_t i = 0; i < NumPpcNodes; i += 1) { const Vst::Node* PpcChildNode = Ppc.GetChildren()[i]; if (PpcChildNode->GetTag() == PpcOp::Expression) { return i; } } ULANG_ERRORF("Malformed Vst : DotIndent cannot be a prefix."); return -1; }(); //~~~~ HANDLE POSTFIXES ~~~~~~~~~~~~~~~~ TSPtr Lhs; for (int32_t i = ExpressionIndex; i < NumPpcNodes; i += 1) { const Vst::Node& PpcChildNode = *Ppc.GetChildren()[i]; switch (PpcChildNode.GetTag()) { case PpcOp::Expression: { Lhs = DesugarExpressionVst(PpcChildNode); } break; // Handle ? case PpcOp::Option: { ULANG_ASSERTF(Lhs, "Expected expr on LHS of QMark"); Lhs = AddMapping(PpcChildNode, TSRef::New(Move(Lhs.AsRef()))); } break; case PpcOp::Pointer: { ULANG_ASSERTF(Lhs, "Expected expr on LHS of Hat"); Lhs = AddMapping(PpcChildNode, TSRef::New(Move(Lhs.AsRef()))); } break; case PpcOp::DotIdentifier: { ULANG_ASSERTF(Lhs, "Expected expr on LHS of DotIdentifier"); const Vst::Identifier& IdentifierNode = PpcChildNode.As(); Lhs = DesugarIdentifier(IdentifierNode, Move(Lhs)); if (IdentifierNode.HasAttributes()) { Lhs->_Attributes = DesugarAttributes(IdentifierNode.GetAux()->GetChildren()); } } break; case PpcOp::SureCall: case PpcOp::FailCall: { ULANG_ASSERTF(Lhs, "Expected expr on LHS of call"); Lhs = DesugarCall( PpcChildNode.GetTag() == PpcOp::FailCall, PpcChildNode.As(), // Arguments Move(Lhs.AsRef()) // Receiver expression ); } break; default: ULANG_ERRORF("Unknown PrePostCall tag!"); break; } } TSRef Rhs = Move(Lhs.AsRef()); //~~~~ HANDLE PREFIXES ~~~~~~~~~~~~~~~~~ // If ExpressionIndex > 0, this expression has prefix subexpressions. if (ExpressionIndex > 0) { // Prefixes are handles right to left. // We start with the expression, and work our way to the left, applying // whatever modifier we might encounter. // e.g. Given `?[]Item` we would have the following `RhsType` // 1. RhsType = Item // 2. RhsType = []RhsType = []Item a.k.a array of items // 3. RhsType = ?RhsType = ?[]Item a.k.a. option array of items //@jira SOL-998 : This use-case needs to be updated for (int32_t i = ExpressionIndex-1; i >= 0; i -= 1) { const TSRef& PpcChildNode = Ppc.GetChildren()[i]; switch (PpcChildNode->GetTag()) { case PpcOp::Expression: { ULANG_ERRORF("Expression should have been processed by the 'HANDLE POSTFIXES' above."); } break; case PpcOp::DotIdentifier: { ULANG_ERRORF("Malformed Vst : DotIndent cannot be a prefix."); } break; case PpcOp::Pointer: { AppendGlitch( PpcChildNode.Get(), EDiagnostic::ErrSemantic_Unsupported, CUTF8String("Non-unique pointers are not supported yet")); Rhs = AddMapping(*PpcChildNode, TSRef::New()); } break; case PpcOp::Option: { Rhs = AddMapping(*PpcChildNode, TSRef::New(Move(Rhs))); } break; case PpcOp::FailCall: { if (PpcChildNode->GetChildCount()) { // Desugar the key expressions. ULANG_ASSERTF(PpcChildNode->IsA(), "Expected prefix [] operand to be a clause"); Vst::Clause& LhsClause = PpcChildNode->As(); TArray> LhsAsts; for (const TSRef& LhsVst : LhsClause.GetChildren()) { LhsAsts.Add(DesugarExpressionVst(*LhsVst)); } Rhs = AddMapping(*PpcChildNode, TSRef::New(Move(LhsAsts), Move(Rhs))); } else { Rhs = AddMapping(*PpcChildNode, TSRef::New(Move(Rhs))); } } break; case PpcOp::SureCall: { AppendGlitch( PpcChildNode.Get(), EDiagnostic::ErrSemantic_Unsupported, CUTF8String("Unsupported: prefix'()' not supported yet")); Rhs = AddMapping(*PpcChildNode, TSRef::New()); } break; default: ULANG_UNREACHABLE(); } } } return Move(Rhs); } TSRef DesugarIdentifier(const Vst::Identifier& IdentifierNode, TSPtr&& Context = nullptr) { if (IdentifierNode.IsQualified()) { if (IdentifierNode.GetChildCount() > 1) { AppendGlitch(IdentifierNode.GetChildren()[0], EDiagnostic::ErrSemantic_ExpectedSingleExpression, "Only one qualifying expression is allowed."); return AddMapping(*IdentifierNode.GetChildren()[0], TSRef::New()); } const CSymbol Symbol = VerifyAddSymbol(&IdentifierNode, IdentifierNode.GetSourceText()); TSRef QualifierAst = DesugarExpressionVst(*IdentifierNode.GetQualification()); return AddMapping(IdentifierNode, TSRef::New(Symbol, Move(Context), Move(QualifierAst))); } else { const CSymbol Symbol = VerifyAddSymbol(&IdentifierNode, IdentifierNode.GetSourceText()); return AddMapping(IdentifierNode, TSRef::New(Symbol, Move(Context))); } } TSRef DesugarFlowIf(const Vst::FlowIf& IfNode) { // All `if` nodes will have clause block children though they may be empty // The simplest forms that can get past the parser (though will have semantic issues) is `if:` and `if ():` const int32_t NumChildren = IfNode.GetChildCount(); const Vst::NodeArray& Clauses = IfNode.GetChildren(); // First, desugar the optional final else clause. TSPtr Result; int32_t Index = NumChildren - 1; if (Clauses[Index]->GetTag() == Vst::FlowIf::ClauseTag::else_body) { Result = DesugarClauseAsCodeBlock(Clauses[Index]->As()); --Index; } // Desugar pairs of clauses into nested CExprIf nodes. // Must be in this order: // - if identifier ] // - condition block |- Repeating // - [then block] ] // - [else block] -- Optional last node // Loop in reverse order, with the first corresponding to the outermost CExprIf. while(Index >= 0) { switch(Clauses[Index]->GetTag()) { case Vst::FlowIf::ClauseTag::if_identifier: { Index--; break; } case Vst::FlowIf::ClauseTag::then_body: { ULANG_ASSERTF(Index > 1, "Clause of FlowIf node is unexpectedly a then clause"); if (Clauses[Index-1]->GetTag() != Vst::FlowIf::ClauseTag::condition) { AppendGlitch(Clauses[Index-1], EDiagnostic::ErrSemantic_MalformedConditional, "Expected condition."); TSRef ErrorNode = TSRef::New(); ErrorNode->AppendChild(Move(Result)); Result = Move(ErrorNode); --Index; } else { const Vst::Clause& Condition = Clauses[Index-1]->As(); TSRef ConditionCodeBlock = DesugarClauseAsCodeBlock(Condition); const Vst::Clause& ThenClause = Clauses[Index]->As(); TSRef ThenCodeBlock = DesugarClauseAsCodeBlock(ThenClause); Result = TSRef::New(Move(ConditionCodeBlock), Move(ThenCodeBlock), Move(Result)); Index -= 2; } break; } case Vst::FlowIf::ClauseTag::condition: { ULANG_ASSERTF(Index > 0, "Clause of FlowIf node is unexpectedly a condition clause"); ULANG_ASSERTF(Clauses[Index-1]->GetTag() == Vst::FlowIf::ClauseTag::if_identifier, "if_identifier clause of FlowIf should precede the condition clause"); const Vst::Clause& Condition = Clauses[Index]->As(); TSRef ConditionCodeBlock = DesugarClauseAsCodeBlock(Condition); --Index; Result = TSRef::New(Move(ConditionCodeBlock), nullptr, Move(Result)); break; } case Vst::FlowIf::ClauseTag::else_body: { AppendGlitch( Clauses[Index], EDiagnostic::ErrSemantic_MalformedConditional, "Expected then clause or condition while parsing `if`."); TSRef ErrorNode = TSRef::New(); ErrorNode->AppendChild(Move(Result)); Result = Move(ErrorNode); Index -= 2; break; } default: ULANG_UNREACHABLE(); }; }; return AddMapping(IfNode, Move(Result.AsRef())); } TSRef DesugarIntLiteral(const Vst::IntLiteral& IntLiteralNode) { // We look back at the mapped Vst node during analysis return AddMapping(IntLiteralNode, TSRef::New()); } TSRef DesugarFloatLiteral(const Vst::FloatLiteral& FloatLiteralNode) { return AddMapping(FloatLiteralNode, TSRef::New()); } TSRef DesugarCharLiteral(const Vst::CharLiteral& CharLiteralNode) { const CUTF8String& String = CharLiteralNode.GetSourceText(); if (String.ByteLen() == 0) { AppendGlitch(&CharLiteralNode, EDiagnostic::ErrSemantic_CharLiteralDoesNotContainOneChar); return AddMapping(CharLiteralNode, TSRef::New()); } if (CharLiteralNode._Format == Vst::CharLiteral::EFormat::UTF8CodeUnit) { // interpret the single byte literally as a code unit return AddMapping(CharLiteralNode, TSRef::New(String[0], CExprChar::EType::UTF8CodeUnit)); } else if (CharLiteralNode._Format == Vst::CharLiteral::EFormat::UnicodeCodePoint) { // decode utf8 to unicode code point SUniCodePointLength CodePointAndLength; CodePointAndLength = CUnicode::DecodeUTF8((UTF8Char*)String.AsCString(), String.ByteLen()); if (CodePointAndLength._ByteLengthUTF8 != uint32_t(String.ByteLen())) { AppendGlitch(&CharLiteralNode, EDiagnostic::ErrSemantic_CharLiteralDoesNotContainOneChar); } return AddMapping(CharLiteralNode, TSRef::New(CodePointAndLength._CodePoint, CExprChar::EType::UnicodeCodePoint)); } else { ULANG_UNREACHABLE(); } } // The extra optional VstNode parameter is used when the string literal is created from a temporary StringLiteralNode. TSRef DesugarStringLiteral(const Vst::StringLiteral& StringLiteralNode) { return AddMapping(StringLiteralNode, TSRef::New(StringLiteralNode.GetSourceText())); } TSRef DesugarPathLiteral(const Vst::PathLiteral& PathLiteralNode) { return AddMapping(PathLiteralNode, TSRef::New(PathLiteralNode.GetSourceText())); } TSRef DesugarInterpolatedString(const Vst::InterpolatedString& InterpolatedStringNode) { const CSymbol ToStringSymbol = _Symbols.AddChecked("ToString"); TSRefArray DesugaredChildren; TSPtr TailString; for (const TSRef& ChildNode : InterpolatedStringNode.GetChildren()) { if (Vst::StringLiteral* StringLiteral = ChildNode->AsNullable()) { if (TailString) { TailString->_String += StringLiteral->GetSourceText(); } else { TSRef StringLiteralAst = DesugarStringLiteral(*StringLiteral); TailString = StringLiteralAst; DesugaredChildren.Add(Move(StringLiteralAst)); } } else if (Vst::Interpolant* Interpolant = ChildNode->AsNullable()) { const Vst::Clause& InterpolantArgClause = Interpolant->GetChildren()[0]->As(); TSRefArray DesugaredInterpolantArgs = DesugarExpressionList(InterpolantArgClause.GetChildren()); // Ignore interpolants that only contained whitespace and comments. if (DesugaredInterpolantArgs.Num()) { if (DesugaredInterpolantArgs.Num() == 1 && DesugaredInterpolantArgs[0]->GetNodeType() == EAstNodeType::Literal_Char) { const CExprChar& Char = static_cast(*DesugaredInterpolantArgs[0]); if (TailString) { TailString->_String += Char.AsString(); } else { TSRef StringLiteralAst = TSRef::New(Char.AsString()); TailString = StringLiteralAst; DesugaredChildren.Add(Move(StringLiteralAst)); } } else { TSRef ToStringArg = MakeExpressionFromExpressionList(Move(DesugaredInterpolantArgs), InterpolantArgClause.GetForm(), InterpolantArgClause, false); TSRef ToStringInvocation = TSRef::New( CExprInvocation::EBracketingStyle::Parentheses, TSRef::New(ToStringSymbol), ToStringArg); DesugaredChildren.Add(AddMapping(*Interpolant, Move(ToStringInvocation))); TailString.Reset(); } } } else { AppendGlitch(ChildNode, EDiagnostic::ErrSemantic_Internal, CUTF8String("Unexpected InterpolatedString child node %s", Vst::GetNodeTypeName(ChildNode->GetElementType()))); } } if (DesugaredChildren.Num() == 1) { return DesugaredChildren[0]; } else if (DesugaredChildren.Num()) { // We are using "Join" instead of "Concatenation" because it is over 40 times faster at this time. const CSymbol JoinSymbol = _Symbols.AddChecked("Join"); TSRef DesugaredChildrenTuple = WrapExpressionListInTuple(Move(DesugaredChildren), InterpolatedStringNode, false); TSRef BlankStringLiteral = TSRef::New(""); TSRef JoinArgs = TSRef::New(DesugaredChildrenTuple, BlankStringLiteral); TSRef JoinInvocation = TSRef::New( CExprInvocation::EBracketingStyle::Parentheses, TSRef::New(JoinSymbol), JoinArgs); return AddMapping(InterpolatedStringNode, Move(JoinInvocation)); } else { return AddMapping(InterpolatedStringNode, TSRef::New("")); } } TSRef DesugarLambda(const Vst::Lambda& LambdaVst) { TSRef DomainAst = DesugarExpressionVst(*LambdaVst.GetChildren()[0]); TSRef RangeAst = DesugarClauseAsExpression(*LambdaVst.GetChildren()[1]); return AddMapping(LambdaVst, TSRef::New(Move(DomainAst), Move(RangeAst))); } TSRef DesugarControl(const Vst::Control& ControlNode) { switch (ControlNode._Keyword) { case Vst::Control::EKeyword::Return: { TSPtr ResultAst; if (ControlNode.GetChildCount() == 1) { ResultAst = DesugarExpressionVst(*ControlNode.GetReturnExpression()); } else if (ControlNode.GetChildCount() > 1) { AppendGlitch( &ControlNode, EDiagnostic::ErrSemantic_UnexpectedNumberOfArguments, "`return` may only have a single sub-expression when returning a result."); return AddMapping(ControlNode, TSRef::New()); } return AddMapping(ControlNode, TSRef::New(Move(ResultAst))); } case Vst::Control::EKeyword::Break: { if (ControlNode.GetChildCount() > 0) { AppendGlitch( &ControlNode, EDiagnostic::ErrSemantic_UnexpectedNumberOfArguments, "`break` may not have any sub-expressions - it does not return a result."); return AddMapping(ControlNode, TSRef::New()); } return AddMapping(ControlNode, TSRef::New()); } case Vst::Control::EKeyword::Yield: AppendGlitch(&ControlNode, EDiagnostic::ErrSemantic_Unimplemented); return AddMapping(ControlNode, TSRef::New()); case Vst::Control::EKeyword::Continue: AppendGlitch(&ControlNode, EDiagnostic::ErrSemantic_Unimplemented); return AddMapping(ControlNode, TSRef::New()); default: return AddMapping(ControlNode, TSRef::New()); } } TSRef DesugarMacro(const Vst::Macro& MacroVst) { const int32_t NumMacroChildren = MacroVst.GetChildCount(); const Vst::Node& MacroNameVst = *MacroVst.GetName(); TSRef MacroCallAst = TSRef::New(DesugarExpressionVst(MacroNameVst), NumMacroChildren); // Populate the clauses in the macro for (int32_t i = 1; i < NumMacroChildren; i += 1) { Vst::Node& ThisMacroChild = *MacroVst.GetChildren()[i]; if (!ThisMacroChild.IsA()) { AppendGlitch(MacroVst.GetChildren()[i].Get(), EDiagnostic::ErrSemantic_MalformedMacro, "Malformed macro: expected a macro clause"); } else { // Add clause and its children to the macro Vst::Clause& ThisClause = ThisMacroChild.As(); const int32_t NumClauseChildren = ThisClause.GetChildCount(); // Don't allow attributes on macro clauses, since they'll otherwise be thrown away at this point. if (ThisClause.HasAttributes()) { AppendGlitch(ThisClause.GetAux()->GetChildren()[0].Get(), EDiagnostic::ErrSemantic_AttributeNotAllowed); } const EMacroClauseTag ClauseTag = [&ThisClause, this]() { using res_t = vsyntax::res_t; switch (ThisClause.GetTag()) { case res_t::res_none: return EMacroClauseTag::None; case res_t::res_of: return EMacroClauseTag::Of; case res_t::res_do: return EMacroClauseTag::Do; case res_t::res_if: case res_t::res_else: case res_t::res_upon: case res_t::res_where: case res_t::res_catch: case res_t::res_then: case res_t::res_until: case res_t::res_return: case res_t::res_yield: case res_t::res_break: case res_t::res_continue: case res_t::res_at: case res_t::res_var: case res_t::res_set: case res_t::res_and: case res_t::res_or: case res_t::res_not: AppendGlitch(&ThisClause, EDiagnostic::ErrSemantic_MalformedMacro, "Malformed macro: reserved word invalid in macro clause"); return EMacroClauseTag::None; case res_t::res_max: default: AppendGlitch(&ThisClause, EDiagnostic::ErrSemantic_MalformedMacro, "Malformed macro: Unknown keyword"); return EMacroClauseTag::None; } }(); TArray> ClauseExprs; ClauseExprs.Reserve(NumClauseChildren); for (const TSRef& ClauseChildVst : ThisClause.GetChildren()) { if(!ClauseChildVst->IsA()) { ClauseExprs.Add(DesugarExpressionVst(*ClauseChildVst)); } } MacroCallAst->AppendClause(CExprMacroCall::CClause(ClauseTag, ThisClause.GetForm(), Move(ClauseExprs))); if (ThisClause.HasAttributes()) { MacroCallAst->_Attributes += DesugarAttributes(ThisClause.GetAux()->GetChildren()); } } } return AddMapping(MacroVst, Move(MacroCallAst)); } TSRefArray DesugarExpressionList(const Vst::NodeArray& Expressions) { TSRefArray DesugaredExpressions; for (const Vst::Node* Child : Expressions) { // Ignore comments in the subexpression list. if (!Child->IsA()) { DesugaredExpressions.Add(DesugarExpressionVst(*Child)); } } return DesugaredExpressions; } TSRef WrapExpressionListInTuple(TSRefArray&& Expressions, const Vst::Node& OriginNode, bool bReciprocalVstMapping) { TSRef Tuple = TSRef::New(Expressions.Num()); Tuple->SetSubExprs(Move(Expressions)); if (bReciprocalVstMapping) { OriginNode.AddMapping(Tuple.Get()); } else { Tuple->SetNonReciprocalMappedVstNode(&OriginNode); } return Tuple; } TSRef WrapExpressionListInCodeBlock(TSRefArray&& Expressions, const Vst::Node& OriginNode, bool bReciprocalVstMapping) { TSRef Block = TSRef::New(Expressions.Num()); Block->SetSubExprs(Move(Expressions)); if (bReciprocalVstMapping) { OriginNode.AddMapping(Block.Get()); } else { Block->SetNonReciprocalMappedVstNode(&OriginNode); } return Block; } TSRef MakeExpressionFromExpressionList(TSRefArray&& DesugaredExpressions, Vst::Clause::EForm Form, const Vst::Node& OriginNode, bool bReciprocalVstMapping = true) { if (DesugaredExpressions.Num() == 1) { // If this is a single expression, return it directly. return DesugaredExpressions[0]; } else if (Form == Vst::Clause::EForm::NoSemicolonOrNewline) { // If this is an empty or comma separated list, create a tuple for the subexpressions. return WrapExpressionListInTuple(Move(DesugaredExpressions), OriginNode, bReciprocalVstMapping); } else { // Otherwise, create a code block for the subexpressions. return WrapExpressionListInCodeBlock(Move(DesugaredExpressions), OriginNode, bReciprocalVstMapping); } } TSRef DesugarExpressionListAsExpression(const Vst::Node& Node, Vst::Clause::EForm Form, bool bReciprocalVstMapping = true) { return MakeExpressionFromExpressionList(DesugarExpressionList(Node.GetChildren()), Form, Node, bReciprocalVstMapping); } TSRef DesugarClauseAsCodeBlock(const Vst::Clause& Clause) { TSRefArray DesugaredChildren = DesugarExpressionList(Clause.GetChildren()); if (DesugaredChildren.Num() > 1 && Clause.GetForm() == Vst::Clause::EForm::NoSemicolonOrNewline) { // If there are multiple comma separated subexpressions, wrap them in a CExprMakeTuple that is // the sole subexpression of the resulting code block. TSRef Tuple = WrapExpressionListInTuple(Move(DesugaredChildren), Clause, false); DesugaredChildren = {Tuple}; } return WrapExpressionListInCodeBlock(Move(DesugaredChildren), Clause, true); } TSRef DesugarParens(const Vst::Parens& Parens) { return DesugarExpressionListAsExpression(Parens, Parens.GetForm()); } TSRef DesugarCommas(const Vst::Commas& Commas) { TSRefArray DesugaredChildren = DesugarExpressionList(Commas.GetChildren()); ULANG_ASSERT(DesugaredChildren.Num() > 1); TSRef Tuple = WrapExpressionListInTuple(Move(DesugaredChildren), Commas, true); // NOTE: (yiliang.siew) This preserves the mistake we shipped in `28.20` where mixed use of separators in // archetype instantiations wrapped the sub-expressions into an implicit `block`, but in other places, it // did not. if (!_Package || _Package->_EffectiveVerseVersion >= Verse::Version::DontMixCommaAndSemicolonInBlocks || VerseFN::UploadedAtFNVersion::EnforceDontMixCommaAndSemicolonInBlocks(_Package->_UploadedAtFNVersion)) { return Tuple; } else { // NOTE: (yiliang.siew) This preserves the old legacy behaviour of potentially wrapping the expression in a // code block/tuple/returning a single expression directly. // This has implications on scoping (since blocks create their own scope and tuples do not) and how // definitions that might previously not have conflicted would conflict if we were not to do this. AppendGlitch(&Commas, EDiagnostic::WarnSemantic_StricterErrorCheck, "Mixing commas with semicolons/newlines in a clause wraps the comma-separated subexpressions in a 'block{...}' " "in the version of Verse you are targeting, but this behavior will change in a future version of Verse. You " "can preserve the current behavior in future versions of Verse by wrapping the comma-separated subexpressions " "in a block{...}.\n" "For example, instead of writing this:\n" " A\n" " B,\n" " C\n" "Write this:\n" " A\n" " block:\n" " B,\n" " C"); return WrapExpressionListInCodeBlock({ Move(Tuple) }, Commas, false); } } TSRef DesugarPlaceholder(const Vst::Placeholder& PlaceholderNode) { return AddMapping(PlaceholderNode, TSRef::New()); } TSRef DesugarEscape(const Vst::Escape& EscapeNode) { AppendGlitch(&EscapeNode, EDiagnostic::ErrSemantic_Unsupported, "Escaped syntax is not yet supported."); return AddMapping(EscapeNode, TSRef::New()); } CSymbol VerifyAddSymbol(const Vst::Node* VstNode, const CUTF8StringView& Text) { TOptional OptionalSymbol = _Symbols.Add(Text); if (!OptionalSymbol.IsSet()) { AppendGlitch(VstNode, EDiagnostic::ErrSemantic_TooLongIdentifier); OptionalSymbol = _Symbols.Add(Text.SubViewBegin(CSymbolTable::MaxSymbolLength-1)); ULANG_ASSERTF(OptionalSymbol.IsSet(), "Truncated name is to long"); } return OptionalSymbol.GetValue(); } template void AppendGlitch(const Vst::Node* VstNode, ResultArgsType&&... ResultArgs) { _Diagnostics.AppendGlitch(SGlitchResult(uLang::ForwardArg(ResultArgs)...), SGlitchLocus(VstNode)); } template TSRef AddMapping(const Vst::Node& VstNode, TSRef&& AstNode) { VstNode.AddMapping(AstNode.Get()); return Move(AstNode); } TArray DesugarAttributes(const TArray>& AttributeVsts) { auto FilterAcceptAll = [](const Vst::Node&)->bool { return true; }; return DesugarAttributesFiltered(AttributeVsts, FilterAcceptAll); } template TArray DesugarAttributesFiltered(const TArray>& AttributeVsts, TPredicate FilterPredicate) { TArray AttributeAsts; for (const TSRef& AttributeWrapperVst : AttributeVsts) { // the actual attribute node is wrapped in a dummy Clause (used to preserve comments // in the VST and tell us whether it's a prepend attribute or append specifier) ULANG_ASSERTF(AttributeWrapperVst->IsA(), "attribute nodes are expected to be wrapped in a dummy Clause node with a single child"); ULANG_ASSERTF(AttributeWrapperVst->GetChildCount() == 1, "attribute nodes are expected to be wrapped in a dummy Clause node with a single child"); const Vst::Clause& AttributeClauseVst = AttributeWrapperVst->As(); SAttribute::EType AttributeType = SAttribute::EType::Attribute; switch(AttributeClauseVst.GetForm()) { case Vst::Clause::EForm::IsPrependAttributeHolder: AttributeType = SAttribute::EType::Attribute; break; case Vst::Clause::EForm::IsAppendAttributeHolder: AttributeType = SAttribute::EType::Specifier; break; case Vst::Clause::EForm::Synthetic: case Vst::Clause::EForm::NoSemicolonOrNewline: case Vst::Clause::EForm::HasSemicolonOrNewline: default: ULANG_UNREACHABLE(); break; } const Vst::Node& AttributeExprVst = *AttributeClauseVst.GetChildren()[0]; if (FilterPredicate(AttributeExprVst)) { TSRef AttributeExprAst = DesugarExpressionVst(AttributeExprVst); SAttribute AttributeAst{ AttributeExprAst, AttributeType }; AttributeAsts.Add(Move(AttributeAst)); } } return AttributeAsts; } TSRef DesugarVst(const Vst::Node& VstNode) { const Vst::NodeType NodeType = VstNode.GetElementType(); switch (NodeType) { case Vst::NodeType::Project: return DesugarProject(static_cast(VstNode)); case Vst::NodeType::Package: return DesugarPackage(static_cast(VstNode)); case Vst::NodeType::Module: return DesugarModule(static_cast(VstNode)); case Vst::NodeType::Snippet: return DesugarSnippet(static_cast(VstNode)); case Vst::NodeType::Where: return DesugarWhere(static_cast(VstNode)); case Vst::NodeType::Mutation: return DesugarMutation(static_cast(VstNode)); case Vst::NodeType::Definition: return DesugarDefinition(static_cast(VstNode)); case Vst::NodeType::Assignment: return DesugarAssignment(static_cast(VstNode)); case Vst::NodeType::BinaryOpLogicalOr: case Vst::NodeType::BinaryOpLogicalAnd: return DesugarBinaryOpLogicalAndOr(VstNode); case Vst::NodeType::PrefixOpLogicalNot: return DesugarPrefixOpLogicalNot(static_cast(VstNode)); case Vst::NodeType::BinaryOpCompare: return DesugarBinaryOpCompare(static_cast(VstNode)); case Vst::NodeType::BinaryOpAddSub: return DesugarBinaryOp(static_cast(VstNode)); case Vst::NodeType::BinaryOpMulDivInfix: return DesugarBinaryOp(static_cast(VstNode)); case Vst::NodeType::BinaryOpRange: return DesugarBinaryOpRange(static_cast(VstNode)); case Vst::NodeType::BinaryOpArrow: return DesugarBinaryOpArrow(static_cast(VstNode)); case Vst::NodeType::TypeSpec: return DesugarTypeSpec(static_cast(VstNode)); case Vst::NodeType::PrePostCall: return DesugarPrePostCall(static_cast(VstNode)); case Vst::NodeType::Identifier: return DesugarIdentifier(static_cast(VstNode)); case Vst::NodeType::Operator: goto unexpected_node_type; case Vst::NodeType::FlowIf: return DesugarFlowIf(static_cast(VstNode)); case Vst::NodeType::IntLiteral: return DesugarIntLiteral(static_cast(VstNode)); case Vst::NodeType::FloatLiteral: return DesugarFloatLiteral(static_cast(VstNode)); case Vst::NodeType::CharLiteral: return DesugarCharLiteral(static_cast(VstNode)); case Vst::NodeType::StringLiteral: return DesugarStringLiteral(static_cast(VstNode)); case Vst::NodeType::PathLiteral: return DesugarPathLiteral(static_cast(VstNode)); case Vst::NodeType::Interpolant: goto unexpected_node_type; case Vst::NodeType::InterpolatedString: return DesugarInterpolatedString(static_cast(VstNode)); case Vst::NodeType::Lambda: return DesugarLambda(static_cast(VstNode)); case Vst::NodeType::Control: return DesugarControl(static_cast(VstNode)); case Vst::NodeType::Macro: return DesugarMacro(VstNode.As()); case Vst::NodeType::Clause: goto unexpected_node_type; case Vst::NodeType::Parens: return DesugarParens(static_cast(VstNode)); case Vst::NodeType::Commas: return DesugarCommas(static_cast(VstNode)); case Vst::NodeType::Placeholder: return DesugarPlaceholder(static_cast(VstNode)); case Vst::NodeType::ParseError: goto unexpected_node_type; case Vst::NodeType::Escape: return DesugarEscape(static_cast(VstNode)); case Vst::NodeType::Comment: goto unexpected_node_type; default: unexpected_node_type: ULANG_ENSUREF(false, "Did not expect this node type (%s) in an expression context.", VstNode.GetElementName()); return AddMapping(VstNode, TSRef::New()); // Return something so semantic analysis can continue } } TSRef DesugarExpressionVst(const Vst::Node& VstNode) { TSRef AstNode = DesugarVst(VstNode); if (AstNode->AsExpression()) { TSRef Expression = Move(AstNode.As()); if (VstNode.HasAttributes()) { Expression->_Attributes = DesugarAttributes(VstNode.GetAux()->GetChildren()); } return Move(Expression); } else { AppendGlitch(&VstNode, EDiagnostic::ErrSyntax_ExpectedExpression); TSRef ErrorExpr = TSRef::New(); ErrorExpr->AppendChild(Move(AstNode)); return AddMapping(VstNode, Move(ErrorExpr)); } } CSymbolTable& _Symbols; CDiagnostics& _Diagnostics; CAstPackage* _Package = nullptr; }; } namespace uLang { TSRef DesugarVstToAst(const Verse::Vst::Project& VstProject, CSymbolTable& Symbols, CDiagnostics& Diagnostics) { CDesugarerImpl DesugarerImpl(Symbols, Diagnostics); return DesugarerImpl.DesugarProject(VstProject); } }