Files
UnrealEngine/Engine/Source/Runtime/VerseCompiler/Private/uLang/SemanticAnalyzer/Desugarer.cpp
2025-05-18 13:04:45 +08:00

2102 lines
95 KiB
C++

// 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<CAstProject> DesugarProject(const Vst::Project& VstProject)
{
// Desugar all the project's packages (and build vertex array for Tarjan's algorithm).
struct SPackageVertex
{
TSRef<CAstPackage> Package;
TArray<int32_t> Dependencies; // Indices of vertices this package depends on
int32_t DepthIndex = IndexNone;
int32_t LowLink = IndexNone;
bool bOnStack = false;
};
TArray<SPackageVertex> Vertices;
Vertices.Reserve(VstProject.GetChildCount());
for (const TSRef<Vst::Node>& 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<const Vst::Package&>(*VstPackage)), {} });
}
// Populate the dependencies for both the Tarjan vertices and the AST packages
for (SPackageVertex& Vertex : Vertices)
{
const Vst::Package* VstPackage = static_cast<const Vst::Package*>(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<CAstProject> AstProject = TSRef<CAstProject>::New(VstProject._Name);
AstProject->ReserveCompilationUnits(VstProject.GetChildCount());
// Run Tarjan's algorithm to generate the compilation units (SCCs)
TArray<int32_t> 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<CAstCompilationUnit> CompilationUnit = TSRef<CAstCompilationUnit>::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<CAstPackage> 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<CAstPackage> AstPackage = TSRef<CAstPackage>::New(
VstPackage._Name,
VstPackage._VersePath,
VstPackage._VerseScope,
VstPackage._Role,
EffectiveVerseVersion,
VstPackage._UploadedAtFNVersion,
VstPackage._VniDestDir.IsSet(),
VstPackage._bTreatModulesAsImplicit,
VstPackage._bAllowExperimental);
TGuardValue<CAstPackage*> CurrentPackageGuard(_Package, AstPackage.Get());
// Desugar all the package's modules or snippets.
for (const TSRef<Vst::Node>& VstNode : VstPackage.GetChildren())
{
if (VstNode->GetElementType() == Vst::NodeType::Module)
{
AstPackage->AppendMember(DesugarModule(static_cast<const Vst::Module&>(*VstNode)));
}
else if (VstNode->GetElementType() == Vst::NodeType::Snippet)
{
AstPackage->AppendMember(DesugarSnippet(static_cast<const Vst::Snippet&>(*VstNode)));
}
else
{
ULANG_ERRORF("Toolchain must ensure that a package only ever contains modules or snippets.");
}
}
VstPackage.AddMapping(&*AstPackage);
return Move(AstPackage);
}
TSRef<CExprModuleDefinition> DesugarModule(const Vst::Module& VstModule)
{
TSRef<CExprModuleDefinition> AstModule = TSRef<CExprModuleDefinition>::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<Vst::Node>& VstNode : VstModule.GetChildren())
{
if (VstNode->GetElementType() == Vst::NodeType::Module)
{
AstModule->AppendMember(DesugarModule(static_cast<const Vst::Module&>(*VstNode)));
}
else if (VstNode->GetElementType() == Vst::NodeType::Snippet)
{
AstModule->AppendMember(DesugarSnippet(static_cast<const Vst::Snippet&>(*VstNode)));
}
else
{
ULANG_ENSUREF(false, "Toolchain must ensure that a module only ever contains modules or snippets.");
}
}
VstModule.AddMapping(&*AstModule);
return Move(AstModule);
}
TSRef<CExprSnippet> DesugarSnippet(const Vst::Snippet& VstSnippet)
{
TSRef<CExprSnippet> AstSnippet = TSRef<CExprSnippet>::New(VstSnippet._Path);
// Desugar all the snippet's top-level expressions.
for (const TSRef<Vst::Node>& VstNode : VstSnippet.GetChildren())
{
if (!VstNode->IsA<Vst::Comment>())
{
AstSnippet->AppendMember(DesugarExpressionVst(*VstNode));
}
}
VstSnippet.AddMapping(&*AstSnippet);
return Move(AstSnippet);
}
TSRef<CExpressionBase> 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<Verse::Vst::Clause>();
// 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<Vst::Comment>())
{
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<CExpressionBase> DesugarWhere(const Vst::Where& WhereVst)
{
TSRef<CExpressionBase> LhsAst = DesugarExpressionVst(*WhereVst.GetLhs());
Vst::Where::RhsView RhsVstView = WhereVst.GetRhs();
TSPtrArray<CExpressionBase> RhsAst;
RhsAst.Reserve(RhsVstView.Num());
for (const TSRef<Vst::Node>& RhsVst : RhsVstView)
{
RhsAst.Add(DesugarExpressionVst(*RhsVst));
}
return AddMapping(WhereVst, TSRef<CExprWhere>::New(Move(LhsAst), Move(RhsAst)));
}
TSRef<CExpressionBase> DesugarMutation(const Vst::Mutation& MutationVst)
{
switch (MutationVst._Keyword)
{
case Vst::Mutation::EKeyword::Var:
return AddMapping(MutationVst, TSRef<CExprVar>::New(DesugarExpressionVst(*MutationVst.Child())));
case Vst::Mutation::EKeyword::Set:
return AddMapping(MutationVst, TSRef<CExprSet>::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<CExpressionBase> DesugarLocalizableOld(
const Vst::Definition& DefinitionVst,
const Vst::Identifier& MessageKeyVst,
const CUTF8String& MessageDefaultText,
const TSRef<Vst::Node>& MessageTypeVst,
const TArray<SNameTypeIdentifierPair>& NameTypePairs,
bool bIsFunction)
{
const CSymbol MessageKeySymbol = VerifyAddSymbol(&MessageKeyVst, MessageKeyVst._OriginalCode);
TArray<TSRef<CExpressionBase>> MapClauseExprs;
const CSymbol MakeLocalizableSymbol = _Symbols.AddChecked("MakeLocalizableValue");
TSRef<CExprIdentifierUnresolved> MakeLocalizableIdentifier = TSRef<CExprIdentifierUnresolved>::New(MakeLocalizableSymbol);
MakeLocalizableIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst);
// ** special exception here to allow looking up this identifier which is <epic_internal> 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<CExprDefinition>
{
const CSymbol NameSymbol = VerifyAddSymbol(NameTypePair.Name, NameTypePair.Name->_OriginalCode);
const CSymbol TypeSymbol = VerifyAddSymbol(NameTypePair.Type, NameTypePair.Type->_OriginalCode);
TSRef<CExpressionBase> NameIdentifier = AddMapping(*NameTypePair.Name, TSRef<CExprIdentifierUnresolved>::New(NameSymbol));
TSRef<CExpressionBase> TypeIdentifier = AddMapping(*NameTypePair.Type, TSRef<CExprIdentifierUnresolved>::New(TypeSymbol));
// create the function parameter definition
TSRef<CExprDefinition> ArgDefinition =
TSRef<CExprDefinition>::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<CExprInvocation> MakeLocalizableValueInvocation =
TSRef<CExprInvocation>::New(
CExprInvocation::EBracketingStyle::Parentheses,
MakeLocalizableIdentifier,
NameIdentifier);
MakeLocalizableValueInvocation->SetNonReciprocalMappedVstNode(&DefinitionVst);
TSRef<CExprString> ArgNameString = TSRef<CExprString>::New(NameTypePair.Name->_OriginalCode);
ArgNameString->SetNonReciprocalMappedVstNode(NameTypePair.Name);
TSRef<CExprFunctionLiteral> MapClauseExpr =
TSRef<CExprFunctionLiteral>::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<CExpressionBase> ElementArguments;
if (NameTypePairs.Num() == 1)
{
ElementArguments = CreateArgDefinition(NameTypePairs[0]);
}
else
{
// multi-parameter functions need to wrap the
// definitions in a CExprMakeTuple node
TSRef<CExprMakeTuple> ElementArgumentsTuple = TSRef<CExprMakeTuple>::New();
ElementArgumentsTuple->SetNonReciprocalMappedVstNode(&DefinitionVst);
for (const SNameTypeIdentifierPair& NameTypePair : NameTypePairs)
{
ElementArgumentsTuple->AppendSubExpr(CreateArgDefinition(NameTypePair));
}
ElementArguments = Move(ElementArgumentsTuple);
}
TSRefArray<CExpressionBase> ArgumentExprs;
{
// Key argument
// for the function case, the current scope will include the function name, so we pass the null symbol here
TSRef<CExprPathPlusSymbol> KeyPath = TSRef<CExprPathPlusSymbol>::New(bIsFunction ? CSymbol() : MessageKeySymbol);
KeyPath->SetNonReciprocalMappedVstNode(&MessageKeyVst);
ArgumentExprs.Add(Move(KeyPath));
}
{
// DefaultText argument
TSRef<CExpressionBase> DefaultTextString = TSRef<CExprString>::New(MessageDefaultText);
ArgumentExprs.Add(Move(DefaultTextString));
}
{
// Substitutions argument
const CSymbol MapSymbol = _Symbols.AddChecked("map");
TSRef<CExprIdentifierUnresolved> MapIdentifier = TSRef<CExprIdentifierUnresolved>::New(MapSymbol);
MapIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst);
TSRef<CExprMacroCall> MapMacroExpr = TSRef<CExprMacroCall>::New(MapIdentifier);
MapMacroExpr->SetNonReciprocalMappedVstNode(&DefinitionVst);
MapMacroExpr->AppendClause(CExprMacroCall::CClause(EMacroClauseTag::None, Verse::Vst::Clause::EForm::Synthetic, Move(MapClauseExprs)));
ArgumentExprs.Add(Move(MapMacroExpr));
}
TSRef<CExprMakeTuple> ArgTuple = WrapExpressionListInTuple(Move(ArgumentExprs), DefinitionVst, false);
const CSymbol MakeMessageSymbol = _Symbols.AddChecked("MakeMessageInternal");
TSRef<CExprIdentifierUnresolved> MakeMessageIdentifier = TSRef<CExprIdentifierUnresolved>::New(MakeMessageSymbol);
// ** special exception here to allow looking up this identifier which is <epic_internal> 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<CExprInvocation> MakeMessageInvocation =
TSRef<CExprInvocation>::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<localizes><override>:message="B"`
TSRef<CExpressionBase> MessageKeyIdentifier = DesugarIdentifier(MessageKeyVst);
if (MessageKeyVst.HasAttributes())
{
MessageKeyIdentifier->_Attributes = DesugarAttributes(MessageKeyVst.GetAux()->GetChildren());
}
TSPtr<CExpressionBase> DefinitionElement;
if (bIsFunction)
{
TSRef<CExprInvocation> ElementInvocation =
TSRef<CExprInvocation>::New(
CExprInvocation::EBracketingStyle::Parentheses,
Move(MessageKeyIdentifier),
Move(ElementArguments.AsRef()));
ElementInvocation->SetNonReciprocalMappedVstNode(&DefinitionVst);
DefinitionElement = ElementInvocation;
}
else
{
DefinitionElement = MessageKeyIdentifier;
}
TSRef<CExprDefinition> Definition = TSRef<CExprDefinition>::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<TSRef<CExpressionBase>>& MapClauseExprs,
const TSPtrArray<CExpressionBase>& Parameters)
{
const CSymbol MakeLocalizableSymbol = _Symbols.AddChecked("MakeLocalizableValue");
TSRef<CExprIdentifierUnresolved> MakeLocalizableIdentifier = TSRef<CExprIdentifierUnresolved>::New(MakeLocalizableSymbol);
MakeLocalizableIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst);
// ** special exception here to allow looking up this identifier which is <epic_internal> 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<CExpressionBase>& Parameter : Parameters)
{
if (Parameter->GetNodeType() == EAstNodeType::Definition)
{
TSPtr<CExprDefinition> ParamDefinition = Parameter.As<CExprDefinition>();
TSPtr<CExpressionBase> Element = ParamDefinition->Element();
if (!Element || Element->GetNodeType() != EAstNodeType::Identifier_Unresolved)
{
continue;
}
TSPtr<CExprIdentifierUnresolved> ParamUnresolved = Element.As<CExprIdentifierUnresolved>();
CSymbol NameSymbol = ParamUnresolved->_Symbol;
const Vst::Node* NameVstNode = ParamUnresolved->GetMappedVstNode();
TSRef<CExprIdentifierUnresolved> NameIdentifier = TSRef<CExprIdentifierUnresolved>::New(NameSymbol);
NameIdentifier->SetNonReciprocalMappedVstNode(ParamUnresolved->GetMappedVstNode());
// create an invocation passing the argument to MakeLocalizableValue
// so it can be added to the Substitutions map
TSRef<CExprInvocation> MakeLocalizableValueInvocation =
TSRef<CExprInvocation>::New(
CExprInvocation::EBracketingStyle::Parentheses,
MakeLocalizableIdentifier,
NameIdentifier);
MakeLocalizableValueInvocation->SetNonReciprocalMappedVstNode(NameVstNode);
TSRef<CExprString> ArgNameString = TSRef<CExprString>::New(NameSymbol.AsString());
ArgNameString->SetNonReciprocalMappedVstNode(NameVstNode);
TSRef<CExprFunctionLiteral> MapClauseExpr =
TSRef<CExprFunctionLiteral>::New(
Move(ArgNameString),
MakeLocalizableValueInvocation);
MapClauseExpr->SetNonReciprocalMappedVstNode(&DefinitionVst);
MapClauseExprs.Add(Move(MapClauseExpr));
}
else if (Parameter->GetNodeType() == EAstNodeType::Invoke_MakeTuple)
{
TSPtr<CExprMakeTuple> ParamTuple = Parameter.As<CExprMakeTuple>();
FillinClauseExprs(DefinitionVst, MapClauseExprs, ParamTuple->GetSubExprs());
}
}
}
TSRef<CExpressionBase> DesugarLocalizable(
const Vst::Definition& DefinitionVst,
const Vst::Identifier& MessageKeyVst,
const CUTF8String& MessageDefaultText,
const TSRef<Vst::Node>& MessageTypeVst,
TSPtrArray<CExpressionBase>& Parameters,
bool bIsFunction)
{
const CSymbol MessageKeySymbol = VerifyAddSymbol(&MessageKeyVst, MessageKeyVst._OriginalCode);
TArray<TSRef<CExpressionBase>> MapClauseExprs;
FillinClauseExprs(DefinitionVst, MapClauseExprs, Parameters);
TSPtr<CExpressionBase> ElementParameters;
if (Parameters.Num() == 1)
{
ElementParameters = Parameters[0];
}
else
{
// multi-parameter functions need to wrap the
// definitions in a CExprMakeTuple node
TSRef<CExprMakeTuple> ElementParametersTuple = TSRef<CExprMakeTuple>::New();
ElementParametersTuple->SetNonReciprocalMappedVstNode(&DefinitionVst);
for (const TSPtr<CExpressionBase>& Parameter : Parameters)
{
ElementParametersTuple->AppendSubExpr(Parameter);
}
ElementParameters = Move(ElementParametersTuple);
}
TSRefArray<CExpressionBase> ArgumentExprs;
{
// Key argument
// for the function case, the current scope will include the function name, so we pass the null symbol here
TSRef<CExprPathPlusSymbol> KeyPath = TSRef<CExprPathPlusSymbol>::New(bIsFunction ? CSymbol() : MessageKeySymbol);
KeyPath->SetNonReciprocalMappedVstNode(&MessageKeyVst);
ArgumentExprs.Add(Move(KeyPath));
}
{
// DefaultText argument
TSRef<CExpressionBase> DefaultTextString = TSRef<CExprString>::New(MessageDefaultText);
DefaultTextString->SetNonReciprocalMappedVstNode(&DefinitionVst);
ArgumentExprs.Add(Move(DefaultTextString));
}
{
// Substitutions argument
const CSymbol MapSymbol = _Symbols.AddChecked("map");
TSRef<CExprIdentifierUnresolved> MapIdentifier = TSRef<CExprIdentifierUnresolved>::New(MapSymbol);
MapIdentifier->SetNonReciprocalMappedVstNode(&DefinitionVst);
TSRef<CExprMacroCall> MapMacroExpr = TSRef<CExprMacroCall>::New(MapIdentifier);
MapMacroExpr->SetNonReciprocalMappedVstNode(&DefinitionVst);
MapMacroExpr->AppendClause(CExprMacroCall::CClause(EMacroClauseTag::None, Verse::Vst::Clause::EForm::Synthetic, Move(MapClauseExprs)));
ArgumentExprs.Add(Move(MapMacroExpr));
}
TSRef<CExprMakeTuple> ArgTuple = WrapExpressionListInTuple(Move(ArgumentExprs), DefinitionVst, false);
const CSymbol MakeMessageSymbol = _Symbols.AddChecked("MakeMessageInternal");
TSRef<CExprIdentifierUnresolved> MakeMessageIdentifier = TSRef<CExprIdentifierUnresolved>::New(MakeMessageSymbol);
// ** special exception here to allow looking up this identifier which is <epic_internal> 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<CExprInvocation> MakeMessageInvocation =
TSRef<CExprInvocation>::New(
CExprInvocation::EBracketingStyle::Parentheses,
Move(MakeMessageIdentifier),
Move(ArgTuple));
MakeMessageInvocation->SetNonReciprocalMappedVstNode(&DefinitionVst);
TSRef<CExpressionBase> MessageKeyIdentifier = AddMapping(MessageKeyVst, TSRef<CExprIdentifierUnresolved>::New(MessageKeySymbol));
if (MessageKeyVst.HasAttributes())
{
MessageKeyIdentifier->_Attributes = DesugarAttributes(MessageKeyVst.GetAux()->GetChildren());
}
TSPtr<CExpressionBase> DefinitionElement;
if (bIsFunction)
{
TSRef<CExprInvocation> ElementInvocation =
TSRef<CExprInvocation>::New(
CExprInvocation::EBracketingStyle::Parentheses,
Move(MessageKeyIdentifier),
Move(ElementParameters.AsRef()));
ElementInvocation->SetNonReciprocalMappedVstNode(&DefinitionVst);
DefinitionElement = ElementInvocation;
}
else
{
DefinitionElement = MessageKeyIdentifier;
}
TSRef<CExprDefinition> Definition = TSRef<CExprDefinition>::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<CExpressionBase> TryDesugarLocalizable(const Vst::Definition& DefinitionVst)
{
TSRef<Vst::Node> 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<localizes> := "The Message"
// 2) TheMsg<localizes> : message = "The Message"
// 3) TheMsg<localizes>(Name:string) := "The Message"
// 4) TheMsg<localizes>(Name:string) : message = "The Message"
// 5) TheMsg<localizes>(Name:string) := "The Message to {Name}"
// 6) TheMsg<localizes>(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<Vst::Node> MaybeLocalizedType;
const Vst::Clause* MaybeLocalizedArgs = nullptr;
if (const Vst::Identifier* LhsIdentifier = LhsVst->AsNullable<Vst::Identifier>())
{
// 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<Vst::TypeSpec>())
{
// 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<Vst::Identifier>();
if (!MaybeLocalizedIdentifier)
{
const Vst::PrePostCall* PrePostCallNode = LhsNode.AsNullable<Vst::PrePostCall>();
if (PrePostCallNode && (PrePostCallNode->GetChildCount() >= 2))
{
TSRef<Vst::Node> PrePostCallFirstChild = PrePostCallNode->GetChildren()[0];
TSRef<Vst::Node> PrePostCallSecondChild = PrePostCallNode->GetChildren()[1];
// this is case 4 and 6
MaybeLocalizedIdentifier = PrePostCallFirstChild->AsNullable<Vst::Identifier>();
MaybeLocalizedArgs = PrePostCallSecondChild->AsNullable<Vst::Clause>();
}
}
}
MaybeLocalizedType = LhsTypeSpec->GetRhs();
}
else if (const Vst::PrePostCall* PrePostCallNode = LhsVst->AsNullable<Vst::PrePostCall>())
{
// this is hit for case 3 and 5
if (PrePostCallNode->GetChildCount() >= 2)
{
TSRef<Vst::Node> PrePostCallFirstChild = PrePostCallNode->GetChildren()[0];
TSRef<Vst::Node> PrePostCallSecondChild = PrePostCallNode->GetChildren()[1];
// this is case 3 and 5
MaybeLocalizedIdentifier = PrePostCallFirstChild->AsNullable<Vst::Identifier>();
MaybeLocalizedArgs = PrePostCallSecondChild->AsNullable<Vst::Clause>();
}
}
if (MaybeLocalizedIdentifier != nullptr)
{
TArray<SNameTypeIdentifierPair> 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<Vst::Node>& ArgNode : MaybeLocalizedArgs->GetChildren())
{
if (ArgNode->GetElementType() == Vst::NodeType::TypeSpec)
{
const Vst::TypeSpec& ArgTypeSpecNode = ArgNode->As<Vst::TypeSpec>();
if (ArgTypeSpecNode.HasLhs())
{
const Vst::Identifier* ArgNameIdentifier = ArgTypeSpecNode.GetLhs()->AsNullable<Vst::Identifier>();
const Vst::Identifier* ArgTypeIdentifier = ArgTypeSpecNode.GetRhs()->AsNullable<Vst::Identifier>();
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<CExprError>::New());
}
TSPtrArray<CExpressionBase> Arguments;
if (bEnableNamedParametersForLocalize)
{
if (MaybeLocalizedArgs)
{
for (const TSRef<Vst::Node>& ParamVst : MaybeLocalizedArgs->GetChildren())
{
Arguments.Add(DesugarExpressionVst(*ParamVst));
}
}
}
// Now get the RHS value
TSRef<Vst::Node> RhsVst = DefinitionVst.GetOperandRight();
TSPtr<Vst::Node> 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<Vst::StringLiteral>())
{
MessageKeyVst = MaybeLocalizedIdentifier;
MessageDefaultText = RhsStringLiteral->GetSourceText();
}
else if (const Vst::InterpolatedString* RhsInterpolatedString = ValueNode->AsNullable<Vst::InterpolatedString>())
{
bool bHasNonLiteralInterpolants = false;
CUTF8StringBuilder DecodedString;
for (const TSRef<Vst::Node>& 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<Vst::StringLiteral>())
{
DecodedString.Append(StringLiteral->GetSourceText());
}
else if (Vst::Interpolant* Interpolant = RhsChildNode->AsNullable<Vst::Interpolant>())
{
const Vst::Clause& InterpolantArgClause = Interpolant->GetChildren()[0]->As<Vst::Clause>();
TSRefArray<CExpressionBase> 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<CExpressionBase> InterpolantArg = DesugaredInterpolantArgs[0];
if (CExprChar* Char = AsNullable<CExprChar>(InterpolantArg))
{
DecodedString.Append(Char->AsString());
}
else if (CExprIdentifierUnresolved* Identifier = AsNullable<CExprIdentifierUnresolved>(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 <localizes> 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<Vst::Macro>())
{
const Vst::Node* RhsMacroName = RhsMacro->GetName();
if (RhsMacroName)
{
const Vst::Identifier* RhsMacroNameIdentifier = RhsMacroName->AsNullable<Vst::Identifier>();
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<CExprError>::New());
}
}
}
return nullptr;
}
TSRef<CExpressionBase> DesugarDefinition(const Vst::Definition& DefinitionVst)
{
TSRef<Vst::Node> LhsVst = DefinitionVst.GetOperandLeft();
TSRef<Vst::Node> RhsVst = DefinitionVst.GetOperandRight();
TSPtr<CExpressionBase> Element;
TSPtr<CExpressionBase> ValueDomain;
CSymbol Name;
TSPtr<CExpressionBase> LocalizableDefinition = TryDesugarLocalizable(DefinitionVst);
if (LocalizableDefinition != nullptr)
{
return LocalizableDefinition.AsRef();
}
if (const Vst::TypeSpec* LhsTypeSpec = LhsVst->AsNullable<Vst::TypeSpec>())
{
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<CExpressionBase> 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<CExprMakeNamed>::New(Name, Move(Element), Move(Value)));
}
TSRef<CExprDefinition> DefinitionAst = TSRef<CExprDefinition>::New(Move(Element), Move(ValueDomain), Move(Value));
if (!Name.IsNull())
{
// Looks like a named parameter
DefinitionAst->SetName(Name);
}
return AddMapping(DefinitionVst, Move(DefinitionAst));
}
TSRef<CExpressionBase> DesugarAssignment(const Vst::Assignment& AssignmentVst)
{
TSRef<Vst::Node> LhsVst = AssignmentVst.GetOperandLeft();
TSRef<Vst::Node> RhsVst = AssignmentVst.GetOperandRight();
Vst::Assignment::EOp Op = RhsVst->GetTag<Vst::Assignment::EOp>();
// Desugar the LHS and RHS subexpressions.
TSRef<CExpressionBase> LhsAst = DesugarExpressionVst(*LhsVst);
TSRef<CExpressionBase> RhsAst = DesugarClauseAsExpression(*RhsVst);
return AddMapping(AssignmentVst, TSRef<CExprAssignment>::New(Op, Move(LhsAst), Move(RhsAst)));
}
TSRef<CExpressionBase> 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<CExprError>::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<CExpressionBase> 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<CExpressionBase> LHS = DesugarExpressionVst(*LHSNode);
// Build expression node
if (bIsLogicalAnd)
{
Result = TSRef<CExprShortCircuitAnd>::New(Move(LHS), Move(Result));
}
else if (bIsLogicalOr)
{
Result = TSRef<CExprShortCircuitOr>::New(Move(LHS), Move(Result));
}
else
{
ULANG_UNREACHABLE();
}
}
// RHS contains the final expression tree for this node
return AddMapping(VstNode, Move(Result));
}
TSRef<CExpressionBase> DesugarPrefixOpLogicalNot(const Vst::PrefixOpLogicalNot& PrefixOpLogicalNotNode)
{
if (PrefixOpLogicalNotNode.GetChildCount() == 0)
{
AppendGlitch(&PrefixOpLogicalNotNode, EDiagnostic::ErrSemantic_PrefixOpNoOperand);
return AddMapping(PrefixOpLogicalNotNode, TSRef<CExprError>::New(nullptr, /*bCanFail=*/true));
}
else
{
const Vst::Node* OperandVst = PrefixOpLogicalNotNode.GetChildren()[0];
TSRef<CExpressionBase> OperandAst = DesugarExpressionVst(*OperandVst);
return AddMapping(PrefixOpLogicalNotNode, TSRef<CExprLogicalNot>::New(Move(OperandAst)));
}
}
TSRef<CExpressionBase> DesugarBinaryOpCompare(const Vst::BinaryOpCompare& BinaryOpCompareNode)
{
const int32_t NumChildren = BinaryOpCompareNode.GetChildCount();
if (NumChildren != 2)
{
AppendGlitch(&BinaryOpCompareNode, EDiagnostic::ErrSemantic_BinaryOpExpectedTwoOperands);
return AddMapping(BinaryOpCompareNode, TSRef<CExprError>::New(nullptr, /*bCanFail=*/true));
}
else
{
const Vst::Node* LhsNode = BinaryOpCompareNode.GetChildren()[0].Get();
TSRef<CExpressionBase> Lhs = DesugarExpressionVst(*LhsNode);
// Get RHS operand
const Vst::Node* RhsNode = BinaryOpCompareNode.GetChildren()[1].Get();
TSRef<CExpressionBase> Rhs = DesugarExpressionVst(*RhsNode);
TSRef<CExprMakeTuple> Argument = TSRef<CExprMakeTuple>::New(Move(Lhs), Move(Rhs));
Argument->SetNonReciprocalMappedVstNode(&BinaryOpCompareNode);
TSRef<CExpressionBase> Result = AddMapping(BinaryOpCompareNode, TSRef<CExprComparison>::New(
RhsNode->GetTag<Vst::BinaryOpCompare::op>(),
Argument));
return Result;
}
}
TSRef<CExpressionBase> 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<CExprError>::New());
}
// Get our first LHS operand
const Vst::Node* LHSNode = BinaryOpNode.GetChildren()[0].Get();
TSPtr<CExpressionBase> 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<Vst::Operator>().GetSourceText();
TSRef<CExpressionBase> Result = DesugarExpressionVst(*OprandNode);
if (OpString[0] == u'-')
{
Result = TSRef<CExprUnaryArithmetic>::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<CExpressionBase> Lhs = LhsPtr.AsRef();
auto HandleMalformedVst = [&Lhs](TSRef<CExpressionBase>&& Rhs)
{
TSRef<CExprError> ErrorExpr = TSRef<CExprError>::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>() == 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>() == EOp::Operand, "Malformed binary op node, expecting an operand.");
TSRef<CExpressionBase> Rhs = DesugarExpressionVst(*RhsOperandNode);
if (OperatorNode->GetElementType() == Vst::NodeType::Operator)
{
const CUTF8String& OpString = OperatorNode->As<Vst::Operator>().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<CExprMakeTuple> Argument = TSRef<CExprMakeTuple>::New(Move(Lhs), Move(Rhs));
Argument->SetNonReciprocalMappedVstNode(OperatorNode);
Lhs = AddMapping(*OperatorNode, TSRef<CExprBinaryArithmetic>::New(
ArithmeticOp,
Argument));
}
else
{
HandleMalformedVst(Move(Rhs));
}
}
else if (OperatorNode->GetElementType() == Vst::NodeType::Identifier)
{
TSRef<CExprMakeTuple> Argument = TSRef<CExprMakeTuple>::New(Move(Lhs), Move(Rhs));
Argument->SetNonReciprocalMappedVstNode(OperatorNode);
TSRef<CExprInvocation> Invocation = TSRef<CExprInvocation>::New(Argument);
const CSymbol OperatorSymbol = VerifyAddSymbol(OperatorNode, CUTF8String("operator'%s'", OperatorNode->As<Vst::Identifier>().GetSourceCStr()));
Invocation->SetCallee(TSRef<CExprIdentifierUnresolved>::New(OperatorSymbol));
Lhs = AddMapping(*OperatorNode, Move(Invocation));
}
else
{
HandleMalformedVst(Move(Rhs));
}
}
}
// LHS contains the final expression tree for this node
return Move(Lhs);
}
TSRef<CExpressionBase> DesugarBinaryOpRange(const Vst::BinaryOpRange& BinaryOpRange)
{
TSRef<CExpressionBase> Lhs = DesugarExpressionVst(*BinaryOpRange.GetChildren()[0]);
TSRef<CExpressionBase> Rhs = DesugarExpressionVst(*BinaryOpRange.GetChildren()[1]);
return AddMapping(BinaryOpRange, TSRef<CExprMakeRange>::New(Move(Lhs), Move(Rhs)));
}
TSRef<CExpressionBase> DesugarBinaryOpArrow(const Vst::BinaryOpArrow& BinaryOpArrow)
{
TSRef<CExpressionBase> Lhs = DesugarExpressionVst(*BinaryOpArrow.GetChildren()[0]);
TSRef<CExpressionBase> Rhs = DesugarExpressionVst(*BinaryOpArrow.GetChildren()[1]);
return AddMapping(BinaryOpArrow, TSRef<CExprArrow>::New(Move(Lhs), Move(Rhs)));
}
TSRef<CExpressionBase> DesugarMaybeNamed(Vst::Node& VstNode, CSymbol& Name)
{
if ((VstNode.GetElementType() == Vst::NodeType::PrePostCall) && (VstNode.GetChildCount() >= 2))
{
TSRef<Vst::Node> VarChild0 = VstNode.GetChildren()[0];
Vst::Node* VarChild1 = VstNode.GetChildren()[1];
// No qualified named parameters yet
if ((VarChild0->GetTag<Vst::PrePostCall::Op>() == Vst::PrePostCall::Op::Option)
&& (VarChild0->GetElementType() == Vst::NodeType::Clause)
&& (VarChild1->GetTag<Vst::PrePostCall::Op>() == Vst::PrePostCall::Op::Expression)
&& (VarChild1->GetElementType() == Vst::NodeType::Identifier))
{
const Vst::Identifier& Identifier = VarChild1->As<Vst::Identifier>();
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<CExpressionBase> 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<CExpressionBase> DesugarTypeSpec(const Vst::TypeSpec& TypeSpecVst)
{
TSPtr<CExpressionBase> Lhs = nullptr;
CSymbol Name;
if (TypeSpecVst.HasLhs())
{
Lhs = DesugarMaybeNamed(*TypeSpecVst.GetLhs(), Name);
}
TSRef<CExpressionBase> Rhs = DesugarExpressionVst(*TypeSpecVst.GetRhs());
TSPtr<CExpressionBase> NoDefaultValue = nullptr;
// Create a CExprDefinition AST node.
TSRef<CExprDefinition> DefinitionAst = TSRef<CExprDefinition>::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<CExpressionBase> DesugarCall(bool bCalledWithBrackets, const Vst::Clause& CallArgs, TSRef<CExpressionBase>&& Callee)
{
// Create an invocation AST node.
return AddMapping(CallArgs, TSRef<CExprInvocation>::New(
bCalledWithBrackets
? CExprInvocation::EBracketingStyle::SquareBrackets
: CExprInvocation::EBracketingStyle::Parentheses,
Move(Callee),
DesugarExpressionListAsExpression(CallArgs, CallArgs.GetForm(), false)));
}
TSRef<CExpressionBase> 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>() == PpcOp::Expression)
{
return i;
}
}
ULANG_ERRORF("Malformed Vst : DotIndent cannot be a prefix.");
return -1;
}();
//~~~~ HANDLE POSTFIXES ~~~~~~~~~~~~~~~~
TSPtr<CExpressionBase> Lhs;
for (int32_t i = ExpressionIndex; i < NumPpcNodes; i += 1)
{
const Vst::Node& PpcChildNode = *Ppc.GetChildren()[i];
switch (PpcChildNode.GetTag<PpcOp>())
{
case PpcOp::Expression:
{
Lhs = DesugarExpressionVst(PpcChildNode);
}
break;
// Handle <expr>?
case PpcOp::Option:
{
ULANG_ASSERTF(Lhs, "Expected expr on LHS of QMark");
Lhs = AddMapping(PpcChildNode, TSRef<CExprQueryValue>::New(Move(Lhs.AsRef())));
}
break;
case PpcOp::Pointer:
{
ULANG_ASSERTF(Lhs, "Expected expr on LHS of Hat");
Lhs = AddMapping(PpcChildNode, TSRef<CExprPointerToReference>::New(Move(Lhs.AsRef())));
}
break;
case PpcOp::DotIdentifier:
{
ULANG_ASSERTF(Lhs, "Expected expr on LHS of DotIdentifier");
const Vst::Identifier& IdentifierNode = PpcChildNode.As<Vst::Identifier>();
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>() == PpcOp::FailCall,
PpcChildNode.As<Vst::Clause>(), // Arguments
Move(Lhs.AsRef()) // Receiver expression
);
}
break;
default: ULANG_ERRORF("Unknown PrePostCall tag!"); break;
}
}
TSRef<CExpressionBase> 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<Vst::Node>& PpcChildNode = Ppc.GetChildren()[i];
switch (PpcChildNode->GetTag<PpcOp>())
{
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<CExprError>::New());
}
break;
case PpcOp::Option:
{
Rhs = AddMapping(*PpcChildNode, TSRef<CExprOptionTypeFormer>::New(Move(Rhs)));
}
break;
case PpcOp::FailCall:
{
if (PpcChildNode->GetChildCount())
{
// Desugar the key expressions.
ULANG_ASSERTF(PpcChildNode->IsA<Vst::Clause>(), "Expected prefix [] operand to be a clause");
Vst::Clause& LhsClause = PpcChildNode->As<Vst::Clause>();
TArray<TSRef<CExpressionBase>> LhsAsts;
for (const TSRef<Vst::Node>& LhsVst : LhsClause.GetChildren())
{
LhsAsts.Add(DesugarExpressionVst(*LhsVst));
}
Rhs = AddMapping(*PpcChildNode, TSRef<CExprMapTypeFormer>::New(Move(LhsAsts), Move(Rhs)));
}
else
{
Rhs = AddMapping(*PpcChildNode, TSRef<CExprArrayTypeFormer>::New(Move(Rhs)));
}
}
break;
case PpcOp::SureCall:
{
AppendGlitch(
PpcChildNode.Get(),
EDiagnostic::ErrSemantic_Unsupported,
CUTF8String("Unsupported: prefix'()' not supported yet"));
Rhs = AddMapping(*PpcChildNode, TSRef<CExprError>::New());
}
break;
default: ULANG_UNREACHABLE();
}
}
}
return Move(Rhs);
}
TSRef<CExpressionBase> DesugarIdentifier(const Vst::Identifier& IdentifierNode, TSPtr<CExpressionBase>&& 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<CExprError>::New());
}
const CSymbol Symbol = VerifyAddSymbol(&IdentifierNode, IdentifierNode.GetSourceText());
TSRef<CExpressionBase> QualifierAst = DesugarExpressionVst(*IdentifierNode.GetQualification());
return AddMapping(IdentifierNode, TSRef<CExprIdentifierUnresolved>::New(Symbol, Move(Context), Move(QualifierAst)));
}
else
{
const CSymbol Symbol = VerifyAddSymbol(&IdentifierNode, IdentifierNode.GetSourceText());
return AddMapping(IdentifierNode, TSRef<CExprIdentifierUnresolved>::New(Symbol, Move(Context)));
}
}
TSRef<CExpressionBase> 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<CExpressionBase> Result;
int32_t Index = NumChildren - 1;
if (Clauses[Index]->GetTag<Vst::FlowIf::ClauseTag>() == Vst::FlowIf::ClauseTag::else_body)
{
Result = DesugarClauseAsCodeBlock(Clauses[Index]->As<Vst::Clause>());
--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<Vst::FlowIf::ClauseTag>())
{
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>() != Vst::FlowIf::ClauseTag::condition)
{
AppendGlitch(Clauses[Index-1], EDiagnostic::ErrSemantic_MalformedConditional, "Expected condition.");
TSRef<CExprError> ErrorNode = TSRef<CExprError>::New();
ErrorNode->AppendChild(Move(Result));
Result = Move(ErrorNode);
--Index;
}
else
{
const Vst::Clause& Condition = Clauses[Index-1]->As<Vst::Clause>();
TSRef<CExprCodeBlock> ConditionCodeBlock = DesugarClauseAsCodeBlock(Condition);
const Vst::Clause& ThenClause = Clauses[Index]->As<Vst::Clause>();
TSRef<CExprCodeBlock> ThenCodeBlock = DesugarClauseAsCodeBlock(ThenClause);
Result = TSRef<CExprIf>::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>() == Vst::FlowIf::ClauseTag::if_identifier, "if_identifier clause of FlowIf should precede the condition clause");
const Vst::Clause& Condition = Clauses[Index]->As<Vst::Clause>();
TSRef<CExprCodeBlock> ConditionCodeBlock = DesugarClauseAsCodeBlock(Condition);
--Index;
Result = TSRef<CExprIf>::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<CExprError> ErrorNode = TSRef<CExprError>::New();
ErrorNode->AppendChild(Move(Result));
Result = Move(ErrorNode);
Index -= 2;
break;
}
default:
ULANG_UNREACHABLE();
};
};
return AddMapping(IfNode, Move(Result.AsRef()));
}
TSRef<CExpressionBase> DesugarIntLiteral(const Vst::IntLiteral& IntLiteralNode)
{
// We look back at the mapped Vst node during analysis
return AddMapping(IntLiteralNode, TSRef<CExprNumber>::New());
}
TSRef<CExpressionBase> DesugarFloatLiteral(const Vst::FloatLiteral& FloatLiteralNode)
{
return AddMapping(FloatLiteralNode, TSRef<CExprNumber>::New());
}
TSRef<CExpressionBase> DesugarCharLiteral(const Vst::CharLiteral& CharLiteralNode)
{
const CUTF8String& String = CharLiteralNode.GetSourceText();
if (String.ByteLen() == 0)
{
AppendGlitch(&CharLiteralNode, EDiagnostic::ErrSemantic_CharLiteralDoesNotContainOneChar);
return AddMapping(CharLiteralNode, TSRef<CExprError>::New());
}
if (CharLiteralNode._Format == Vst::CharLiteral::EFormat::UTF8CodeUnit)
{
// interpret the single byte literally as a code unit
return AddMapping(CharLiteralNode, TSRef<CExprChar>::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<CExprChar>::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<CExprString> DesugarStringLiteral(const Vst::StringLiteral& StringLiteralNode)
{
return AddMapping(StringLiteralNode, TSRef<CExprString>::New(StringLiteralNode.GetSourceText()));
}
TSRef<CExpressionBase> DesugarPathLiteral(const Vst::PathLiteral& PathLiteralNode)
{
return AddMapping(PathLiteralNode, TSRef<CExprPath>::New(PathLiteralNode.GetSourceText()));
}
TSRef<CExpressionBase> DesugarInterpolatedString(const Vst::InterpolatedString& InterpolatedStringNode)
{
const CSymbol ToStringSymbol = _Symbols.AddChecked("ToString");
TSRefArray<CExpressionBase> DesugaredChildren;
TSPtr<CExprString> TailString;
for (const TSRef<Vst::Node>& ChildNode : InterpolatedStringNode.GetChildren())
{
if (Vst::StringLiteral* StringLiteral = ChildNode->AsNullable<Vst::StringLiteral>())
{
if (TailString)
{
TailString->_String += StringLiteral->GetSourceText();
}
else
{
TSRef<CExprString> StringLiteralAst = DesugarStringLiteral(*StringLiteral);
TailString = StringLiteralAst;
DesugaredChildren.Add(Move(StringLiteralAst));
}
}
else if (Vst::Interpolant* Interpolant = ChildNode->AsNullable<Vst::Interpolant>())
{
const Vst::Clause& InterpolantArgClause = Interpolant->GetChildren()[0]->As<Vst::Clause>();
TSRefArray<CExpressionBase> 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<const CExprChar&>(*DesugaredInterpolantArgs[0]);
if (TailString)
{
TailString->_String += Char.AsString();
}
else
{
TSRef<CExprString> StringLiteralAst = TSRef<CExprString>::New(Char.AsString());
TailString = StringLiteralAst;
DesugaredChildren.Add(Move(StringLiteralAst));
}
}
else
{
TSRef<CExpressionBase> ToStringArg = MakeExpressionFromExpressionList(Move(DesugaredInterpolantArgs), InterpolantArgClause.GetForm(), InterpolantArgClause, false);
TSRef<CExprInvocation> ToStringInvocation = TSRef<CExprInvocation>::New(
CExprInvocation::EBracketingStyle::Parentheses,
TSRef<CExprIdentifierUnresolved>::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<CExpressionBase> DesugaredChildrenTuple = WrapExpressionListInTuple(Move(DesugaredChildren), InterpolatedStringNode, false);
TSRef<CExprString> BlankStringLiteral = TSRef<CExprString>::New("");
TSRef<CExpressionBase> JoinArgs = TSRef<CExprMakeTuple>::New(DesugaredChildrenTuple, BlankStringLiteral);
TSRef<CExprInvocation> JoinInvocation = TSRef<CExprInvocation>::New(
CExprInvocation::EBracketingStyle::Parentheses,
TSRef<CExprIdentifierUnresolved>::New(JoinSymbol),
JoinArgs);
return AddMapping(InterpolatedStringNode, Move(JoinInvocation));
}
else
{
return AddMapping(InterpolatedStringNode, TSRef<CExprString>::New(""));
}
}
TSRef<CExpressionBase> DesugarLambda(const Vst::Lambda& LambdaVst)
{
TSRef<CExpressionBase> DomainAst = DesugarExpressionVst(*LambdaVst.GetChildren()[0]);
TSRef<CExpressionBase> RangeAst = DesugarClauseAsExpression(*LambdaVst.GetChildren()[1]);
return AddMapping(LambdaVst, TSRef<CExprFunctionLiteral>::New(Move(DomainAst), Move(RangeAst)));
}
TSRef<CExpressionBase> DesugarControl(const Vst::Control& ControlNode)
{
switch (ControlNode._Keyword)
{
case Vst::Control::EKeyword::Return:
{
TSPtr<CExpressionBase> 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<CExprError>::New());
}
return AddMapping(ControlNode, TSRef<CExprReturn>::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<CExprError>::New());
}
return AddMapping(ControlNode, TSRef<CExprBreak>::New());
}
case Vst::Control::EKeyword::Yield:
AppendGlitch(&ControlNode, EDiagnostic::ErrSemantic_Unimplemented);
return AddMapping(ControlNode, TSRef<CExprError>::New());
case Vst::Control::EKeyword::Continue:
AppendGlitch(&ControlNode, EDiagnostic::ErrSemantic_Unimplemented);
return AddMapping(ControlNode, TSRef<CExprError>::New());
default:
return AddMapping(ControlNode, TSRef<CExprError>::New());
}
}
TSRef<CExpressionBase> DesugarMacro(const Vst::Macro& MacroVst)
{
const int32_t NumMacroChildren = MacroVst.GetChildCount();
const Vst::Node& MacroNameVst = *MacroVst.GetName();
TSRef<CExprMacroCall> MacroCallAst = TSRef<CExprMacroCall>::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<Vst::Clause>())
{
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<Vst::Clause>();
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<res_t>())
{
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<TSRef<CExpressionBase>> ClauseExprs;
ClauseExprs.Reserve(NumClauseChildren);
for (const TSRef<Vst::Node>& ClauseChildVst : ThisClause.GetChildren())
{
if(!ClauseChildVst->IsA<Vst::Comment>())
{
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<CExpressionBase> DesugarExpressionList(const Vst::NodeArray& Expressions)
{
TSRefArray<CExpressionBase> DesugaredExpressions;
for (const Vst::Node* Child : Expressions)
{
// Ignore comments in the subexpression list.
if (!Child->IsA<Vst::Comment>())
{
DesugaredExpressions.Add(DesugarExpressionVst(*Child));
}
}
return DesugaredExpressions;
}
TSRef<CExprMakeTuple> WrapExpressionListInTuple(TSRefArray<CExpressionBase>&& Expressions, const Vst::Node& OriginNode, bool bReciprocalVstMapping)
{
TSRef<CExprMakeTuple> Tuple = TSRef<CExprMakeTuple>::New(Expressions.Num());
Tuple->SetSubExprs(Move(Expressions));
if (bReciprocalVstMapping)
{
OriginNode.AddMapping(Tuple.Get());
}
else
{
Tuple->SetNonReciprocalMappedVstNode(&OriginNode);
}
return Tuple;
}
TSRef<CExprCodeBlock> WrapExpressionListInCodeBlock(TSRefArray<CExpressionBase>&& Expressions, const Vst::Node& OriginNode, bool bReciprocalVstMapping)
{
TSRef<CExprCodeBlock> Block = TSRef<CExprCodeBlock>::New(Expressions.Num());
Block->SetSubExprs(Move(Expressions));
if (bReciprocalVstMapping)
{
OriginNode.AddMapping(Block.Get());
}
else
{
Block->SetNonReciprocalMappedVstNode(&OriginNode);
}
return Block;
}
TSRef<CExpressionBase> MakeExpressionFromExpressionList(TSRefArray<CExpressionBase>&& 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<CExpressionBase> DesugarExpressionListAsExpression(const Vst::Node& Node, Vst::Clause::EForm Form, bool bReciprocalVstMapping = true)
{
return MakeExpressionFromExpressionList(DesugarExpressionList(Node.GetChildren()), Form, Node, bReciprocalVstMapping);
}
TSRef<CExprCodeBlock> DesugarClauseAsCodeBlock(const Vst::Clause& Clause)
{
TSRefArray<CExpressionBase> 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<CExprMakeTuple> Tuple = WrapExpressionListInTuple(Move(DesugaredChildren), Clause, false);
DesugaredChildren = {Tuple};
}
return WrapExpressionListInCodeBlock(Move(DesugaredChildren), Clause, true);
}
TSRef<CExpressionBase> DesugarParens(const Vst::Parens& Parens)
{
return DesugarExpressionListAsExpression(Parens, Parens.GetForm());
}
TSRef<CExpressionBase> DesugarCommas(const Vst::Commas& Commas)
{
TSRefArray<CExpressionBase> DesugaredChildren = DesugarExpressionList(Commas.GetChildren());
ULANG_ASSERT(DesugaredChildren.Num() > 1);
TSRef<CExprMakeTuple> 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<CExpressionBase> DesugarPlaceholder(const Vst::Placeholder& PlaceholderNode)
{
return AddMapping(PlaceholderNode, TSRef<CExprPlaceholder>::New());
}
TSRef<CExpressionBase> DesugarEscape(const Vst::Escape& EscapeNode)
{
AppendGlitch(&EscapeNode, EDiagnostic::ErrSemantic_Unsupported, "Escaped syntax is not yet supported.");
return AddMapping(EscapeNode, TSRef<CExprError>::New());
}
CSymbol VerifyAddSymbol(const Vst::Node* VstNode, const CUTF8StringView& Text)
{
TOptional<CSymbol> 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<typename... ResultArgsType>
void AppendGlitch(const Vst::Node* VstNode, ResultArgsType&&... ResultArgs)
{
_Diagnostics.AppendGlitch(SGlitchResult(uLang::ForwardArg<ResultArgsType>(ResultArgs)...), SGlitchLocus(VstNode));
}
template<typename ExpressionType>
TSRef<ExpressionType> AddMapping(const Vst::Node& VstNode, TSRef<ExpressionType>&& AstNode)
{
VstNode.AddMapping(AstNode.Get());
return Move(AstNode);
}
TArray<SAttribute> DesugarAttributes(const TArray<TSRef<Vst::Node>>& AttributeVsts)
{
auto FilterAcceptAll = [](const Vst::Node&)->bool { return true; };
return DesugarAttributesFiltered(AttributeVsts, FilterAcceptAll);
}
template<typename TPredicate>
TArray<SAttribute> DesugarAttributesFiltered(const TArray<TSRef<Vst::Node>>& AttributeVsts, TPredicate FilterPredicate)
{
TArray<SAttribute> AttributeAsts;
for (const TSRef<Vst::Node>& 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<Vst::Clause>(), "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<Vst::Clause>();
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<CExpressionBase> AttributeExprAst = DesugarExpressionVst(AttributeExprVst);
SAttribute AttributeAst{ AttributeExprAst, AttributeType };
AttributeAsts.Add(Move(AttributeAst));
}
}
return AttributeAsts;
}
TSRef<CAstNode> DesugarVst(const Vst::Node& VstNode)
{
const Vst::NodeType NodeType = VstNode.GetElementType();
switch (NodeType)
{
case Vst::NodeType::Project: return DesugarProject(static_cast<const Vst::Project&>(VstNode));
case Vst::NodeType::Package: return DesugarPackage(static_cast<const Vst::Package&>(VstNode));
case Vst::NodeType::Module: return DesugarModule(static_cast<const Vst::Module&>(VstNode));
case Vst::NodeType::Snippet: return DesugarSnippet(static_cast<const Vst::Snippet&>(VstNode));
case Vst::NodeType::Where: return DesugarWhere(static_cast<const Vst::Where&>(VstNode));
case Vst::NodeType::Mutation: return DesugarMutation(static_cast<const Vst::Mutation&>(VstNode));
case Vst::NodeType::Definition: return DesugarDefinition(static_cast<const Vst::Definition&>(VstNode));
case Vst::NodeType::Assignment: return DesugarAssignment(static_cast<const Vst::Assignment&>(VstNode));
case Vst::NodeType::BinaryOpLogicalOr:
case Vst::NodeType::BinaryOpLogicalAnd: return DesugarBinaryOpLogicalAndOr(VstNode);
case Vst::NodeType::PrefixOpLogicalNot: return DesugarPrefixOpLogicalNot(static_cast<const Vst::PrefixOpLogicalNot&>(VstNode));
case Vst::NodeType::BinaryOpCompare: return DesugarBinaryOpCompare(static_cast<const Vst::BinaryOpCompare&>(VstNode));
case Vst::NodeType::BinaryOpAddSub: return DesugarBinaryOp(static_cast<const Vst::BinaryOpAddSub&>(VstNode));
case Vst::NodeType::BinaryOpMulDivInfix: return DesugarBinaryOp(static_cast<const Vst::BinaryOpMulDivInfix&>(VstNode));
case Vst::NodeType::BinaryOpRange: return DesugarBinaryOpRange(static_cast<const Vst::BinaryOpRange&>(VstNode));
case Vst::NodeType::BinaryOpArrow: return DesugarBinaryOpArrow(static_cast<const Vst::BinaryOpArrow&>(VstNode));
case Vst::NodeType::TypeSpec: return DesugarTypeSpec(static_cast<const Vst::TypeSpec&>(VstNode));
case Vst::NodeType::PrePostCall: return DesugarPrePostCall(static_cast<const Vst::PrePostCall&>(VstNode));
case Vst::NodeType::Identifier: return DesugarIdentifier(static_cast<const Vst::Identifier&>(VstNode));
case Vst::NodeType::Operator: goto unexpected_node_type;
case Vst::NodeType::FlowIf: return DesugarFlowIf(static_cast<const Vst::FlowIf&>(VstNode));
case Vst::NodeType::IntLiteral: return DesugarIntLiteral(static_cast<const Vst::IntLiteral&>(VstNode));
case Vst::NodeType::FloatLiteral: return DesugarFloatLiteral(static_cast<const Vst::FloatLiteral&>(VstNode));
case Vst::NodeType::CharLiteral: return DesugarCharLiteral(static_cast<const Vst::CharLiteral&>(VstNode));
case Vst::NodeType::StringLiteral: return DesugarStringLiteral(static_cast<const Vst::StringLiteral&>(VstNode));
case Vst::NodeType::PathLiteral: return DesugarPathLiteral(static_cast<const Vst::PathLiteral&>(VstNode));
case Vst::NodeType::Interpolant: goto unexpected_node_type;
case Vst::NodeType::InterpolatedString: return DesugarInterpolatedString(static_cast<const Vst::InterpolatedString&>(VstNode));
case Vst::NodeType::Lambda: return DesugarLambda(static_cast<const Vst::Lambda&>(VstNode));
case Vst::NodeType::Control: return DesugarControl(static_cast<const Vst::Control&>(VstNode));
case Vst::NodeType::Macro: return DesugarMacro(VstNode.As<Vst::Macro>());
case Vst::NodeType::Clause: goto unexpected_node_type;
case Vst::NodeType::Parens: return DesugarParens(static_cast<const Vst::Parens&>(VstNode));
case Vst::NodeType::Commas: return DesugarCommas(static_cast<const Vst::Commas&>(VstNode));
case Vst::NodeType::Placeholder: return DesugarPlaceholder(static_cast<const Vst::Placeholder&>(VstNode));
case Vst::NodeType::ParseError: goto unexpected_node_type;
case Vst::NodeType::Escape: return DesugarEscape(static_cast<const Vst::Escape&>(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<CExprError>::New()); // Return something so semantic analysis can continue
}
}
TSRef<CExpressionBase> DesugarExpressionVst(const Vst::Node& VstNode)
{
TSRef<CAstNode> AstNode = DesugarVst(VstNode);
if (AstNode->AsExpression())
{
TSRef<CExpressionBase> Expression = Move(AstNode.As<CExpressionBase>());
if (VstNode.HasAttributes())
{
Expression->_Attributes = DesugarAttributes(VstNode.GetAux()->GetChildren());
}
return Move(Expression);
}
else
{
AppendGlitch(&VstNode, EDiagnostic::ErrSyntax_ExpectedExpression);
TSRef<CExprError> ErrorExpr = TSRef<CExprError>::New();
ErrorExpr->AppendChild(Move(AstNode));
return AddMapping(VstNode, Move(ErrorExpr));
}
}
CSymbolTable& _Symbols;
CDiagnostics& _Diagnostics;
CAstPackage* _Package = nullptr;
};
}
namespace uLang
{
TSRef<CAstProject> DesugarVstToAst(const Verse::Vst::Project& VstProject, CSymbolTable& Symbols, CDiagnostics& Diagnostics)
{
CDesugarerImpl DesugarerImpl(Symbols, Diagnostics);
return DesugarerImpl.DesugarProject(VstProject);
}
}