Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1436 lines
31 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "SDCECommon.h"
#include "SDCETokenizer.h"
#include "SDCEOpBuffer.h"
namespace UE::ShaderMinifier::SDCE
{
/** Standardized precedence for all operations */
enum class EPrecedence
{
None,
Primary,
Access,
Declare,
BinaryGeneric,
BinaryAssign,
Statement
};
/**
* As per its name, the *shallow* parser doesn't require full program understanding
* it instead attempts to parse what it can, and treat the rest as "complex". It's up
* to the visitor to interpret this as it sees fit.
*/
class FShallowParser
{
public:
FShallowParser()
{
// Let's set up a standardized set of templated types. This is not complete, nor does it aim to be,
// just to get "enough" stuff through.
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("texture")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Texture1D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Texture1DArray")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Texture1D_Array")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Texture2D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Texture2DArray")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Texture2D_Array")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Texture2DMS")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("MS_Texture2D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Texture2DMSArray")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("MS_Texture2D_Array")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Texture3D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("TextureCube")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("TextureCubeArray")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("TextureCube_Array")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("Buffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("DataBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("AppendStructuredBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("AppendRegularBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("ByteAddressBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("ByteBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("ConsumeStructuredBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("ConsumeRegularBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RWBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RW_DataBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RWByteAddressBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RW_ByteBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RWStructuredBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RW_RegularBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RWTexture1D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RW_Texture1D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RWTexture1DArray")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RW_Texture1D_Array")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RWTexture2D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RW_Texture2D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RWTexture2DArray")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RW_Texture2D_Array")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RasterizerOrderedTexture2D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RWTexture3D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RW_Texture3D")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("StructuredBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RegularBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("ConstantBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("RaytracingAccelerationStructure")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("TriangleStream")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("ConstantBuffer")));
TemplatedTypes.Add(FConstSmallStringView::Text(TEXT("vector")));
}
/**
* Parse a code chunk, visitor statically invoked
*/
template<typename T>
void ParseCode(const FParseViewType& Code, T&& Visitor)
{
// Useful for debugging
#if 0
Type = ECodeChunkType::Function;
Code = R"()";
#endif // 0
// Setup tokenizer
FShallowTokenizer Tk;
Tk.Begin = Code.Begin;
Tk.End = Code.Begin + Code.Length;
Tk.Next();
// Just keep trying to parse as statements
while (!Tk.IsEOS())
{
ParseStatement(Tk);
}
// Succeeded, propagate visitation
for (int32 i = 0; i < OpBuffer.Num(); i++)
{
const FVisitOp& Op = OpBuffer[i];
switch (Op.Type)
{
default:
{
checkNoEntry();
break;
}
case EVisitOp::ID: {
Visitor.VisitPrimaryID(Op.ID);
break;
}
case EVisitOp::ComplexID: {
Visitor.VisitComplexID(Op.ID);
break;
}
case EVisitOp::Type: {
Visitor.VisitType(Op.ID);
break;
}
case EVisitOp::Binary: {
Visitor.VisitBinary(Op.Binary.Token);
break;
}
case EVisitOp::MemberAccess: {
Visitor.VisitPrimaryMemberAccess(Op.ID);
break;
}
case EVisitOp::AggregateComposite: {
Visitor.VisitAggregateComposite();
break;
}
case EVisitOp::Declaration: {
Visitor.VisitDeclaration();
break;
}
case EVisitOp::StatementPrologue: {
Visitor.VisitStatementPrologue(Op.Statement.Cursor);
break;
}
case EVisitOp::StatementEpilogue: {
Visitor.VisitStatementEpilogue(Op.Statement.Cursor);
break;
}
case EVisitOp::StructPrologue: {
Visitor.VisitStructPrologue(Op.Struct.ID);
break;
}
case EVisitOp::StructEpilogue: {
Visitor.VisitStructEpilogue();
break;
}
case EVisitOp::FunctionPrologue: {
Visitor.VisitFunctionPrologue();
break;
}
case EVisitOp::FunctionEpilogue: {
Visitor.VisitFunctionEpilogue();
break;
}
case EVisitOp::FramePrologue: {
Visitor.VisitFramePrologue();
break;
}
case EVisitOp::FrameEpilogue: {
Visitor.VisitFrameEpilogue();
break;
}
case EVisitOp::Alias: {
Visitor.VisitAlias();
break;
}
case EVisitOp::Drain: {
Visitor.VisitDrain(Op.Drain.Type, Op.Drain.Reason);
break;
}
}
}
OpBuffer.Empty();
Tk.OpHead = TokenizerInvalidOpHead;
}
private:
/** Helper, appends prologue/epilogue around a statement */
template<typename F>
void WithStatement(FShallowTokenizer& Tk, F&& functor)
{
OpBuffer.Add(FVisitOp { .Type = EVisitOp::StatementPrologue, .Statement = { .Cursor = Tk.Token.Begin } });
functor();
OpBuffer.Add(FVisitOp { .Type = EVisitOp::StatementEpilogue, .Statement = { .Cursor = Tk.Token.Begin + Tk.Token.Length } });
}
void ParseStruct(FShallowTokenizer& Tk)
{
bool bIsTemplated = SkipTemplate(Tk);
SkipAnnotations(Tk);
check(Tk.IsConsume(ETokenType::Struct));
FToken ID = Tk.Next();
// If templated, keep track of it
if (bIsTemplated)
{
TemplatedTypes.Add(FSmallStringView::Get(ID));
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::StructPrologue, .Struct = { FSmallStringView::Get(ID) } });
if (!Tk.Is(ETokenType::BodyOpen) && !Tk.Is(ETokenType::EndOfStatement))
{
// TODO: We need structural "skipping" with complex handlers
Tk.SkipUntil(TEXT("{;"));
}
if (Tk.Is(ETokenType::BodyOpen))
{
ParseBody(Tk);
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::StructEpilogue });
}
bool ParseBody(FShallowTokenizer& Tk)
{
check(Tk.Is(ETokenType::BodyOpen));
Tk.Next();
ParseBodyInner(Tk);
return Tk.IsConsume(ETokenType::BodyClose);
}
void ParseBodyInner(FShallowTokenizer& Tk)
{
while (!Tk.IsEOS() && !Tk.Is(ETokenType::BodyClose))
{
ParseStatement(Tk);
}
}
void ParseStatement(FShallowTokenizer& Tk, bool bConsumeEOS = true)
{
OpBuffer.Add(FVisitOp { .Type = EVisitOp::StatementPrologue, .Statement = { .Cursor = Tk.Token.Begin } });
if (!TryParseStatementInside(Tk))
{
OpBuffer.Add(FVisitOp { .Type = EVisitOp::StatementEpilogue, .Statement = { .Cursor = Tk.Begin } });
SkipStatement(Tk);
return;
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::StatementEpilogue, .Statement = { .Cursor = Tk.Token.Begin + Tk.Token.Length } });
if (bConsumeEOS)
{
Tk.IsConsume(ETokenType::EndOfStatement);
}
}
void ParseReturn(FShallowTokenizer& Tk)
{
int32 DrainAnchor = OpBuffer.Num();
Tk.Next();
if (!Tk.Is(ETokenType::EndOfStatement))
{
TryParseExpression(Tk);
PushDrain(DrainAnchor, TEXT("Return"));
}
}
void ParseDXCPragma(FShallowTokenizer& Tk)
{
Tk.Next();
Tk.SkipUntil(')');
Tk.Next();
}
bool ParseIf(FShallowTokenizer& Tk)
{
bool bFirst = true;
while (bFirst || Tk.IsConsume(ETokenType::Else))
{
bFirst = false;
if (Tk.IsConsume(ETokenType::If))
{
if (!Tk.IsConsume(ETokenType::ParenthesisOpen))
{
return false;
}
WithStatement(Tk, [&] { TryParseExpression(Tk); });
if (!Tk.Is(ETokenType::ParenthesisClose))
{
return false;
}
Tk.Next();
}
ParseStatementBody(Tk);
}
return true;
}
bool ParseWhile(FShallowTokenizer& Tk)
{
Tk.Next();
if (!Tk.IsConsume(ETokenType::ParenthesisOpen))
{
return false;
}
WithStatement(Tk, [&] { TryParseExpression(Tk); });
if (!Tk.IsConsume(ETokenType::ParenthesisClose))
{
return false;
}
ParseStatementBody(Tk);
return true;
}
bool ParseFor(FShallowTokenizer& Tk)
{
Tk.Next();
if (!Tk.IsConsume(ETokenType::ParenthesisOpen))
{
return false;
}
// Initializer
if (!Tk.IsConsume(ETokenType::EndOfStatement))
{
ParseStatement(Tk, false);
if (!Tk.IsConsume(ETokenType::EndOfStatement))
{
return false;
}
}
// Condition
if (!Tk.IsConsume(ETokenType::EndOfStatement))
{
ParseStatement(Tk, false);
if (!Tk.IsConsume(ETokenType::EndOfStatement))
{
return false;
}
}
// Step
if (!Tk.Is(ETokenType::ParenthesisClose))
{
ParseStatement(Tk);
}
if (!Tk.IsConsume(ETokenType::ParenthesisClose))
{
return false;
}
ParseStatementBody(Tk);
return true;
}
void ParseStatementBody(FShallowTokenizer& Tk)
{
if (Tk.Is(ETokenType::BodyOpen))
{
ParseBody(Tk);
}
else
{
ParseStatement(Tk);
}
}
bool ParseSwitch(FShallowTokenizer& Tk)
{
Tk.Next();
if (!Tk.IsConsume(ETokenType::ParenthesisOpen))
{
return false;
}
int32 DrainAnchor = OpBuffer.Num();
TryParseExpression(Tk);
PushDrain(DrainAnchor, TEXT("Switch-Cond"));
if(!Tk.IsConsume(ETokenType::ParenthesisClose))
{
return false;
}
if(!Tk.IsConsume(ETokenType::BodyOpen))
{
return false;
}
while (!Tk.IsEOS() && !Tk.Is(ETokenType::BodyClose))
{
if (Tk.IsConsume(ETokenType::Default))
{
}
else if (Tk.IsConsume(ETokenType::Case))
{
if (!TryParsePrimary(Tk))
{
break;
}
PushDrain(DrainAnchor, TEXT("Switch-Case"));
}
else
{
break;
}
if (!Tk.IsConsume(ETokenType::Colon))
{
break;
}
// Fallthrough?
if (Tk.Is(ETokenType::Case))
{
continue;
}
while (!Tk.Is(ETokenType::BodyClose) && !Tk.Is(ETokenType::Default) && !Tk.Is(ETokenType::Case))
{
ParseStatement(Tk);
}
}
return Tk.IsConsume(ETokenType::BodyClose);
}
void ParseBreak(FShallowTokenizer& Tk)
{
Tk.Next();
}
bool TryParseStatementInside(FShallowTokenizer& Tk)
{
// For now, just ignore all annotations
SkipAnnotations(Tk);
switch (Tk.Token.Type)
{
default:
{
break;
}
case ETokenType::Return:
{
ParseReturn(Tk);
return true;
}
case ETokenType::If:
{
return ParseIf(Tk);
}
case ETokenType::DXCPragma:
{
ParseDXCPragma(Tk);
return true;
}
case ETokenType::While:
{
return ParseWhile(Tk);
}
case ETokenType::Switch:
{
return ParseSwitch(Tk);
}
case ETokenType::For:
{
return ParseFor(Tk);
}
case ETokenType::Struct:
{
ParseStruct(Tk);
return true;
}
case ETokenType::BodyOpen:
{
return ParseBody(Tk);
}
case ETokenType::Break:
{
ParseBreak(Tk);
return true;
}
}
if (TryParseFunction(Tk))
{
return true;
}
if (TryParseDeclaration(Tk))
{
return true;
}
if (TryParseExpression(Tk))
{
return true;
}
return false;
}
void SkipStatement(FShallowTokenizer& Tk)
{
if (!Tk.IsConsume(ETokenType::EndOfStatement))
{
// TODO: Structural skipping!
FParseCharType* Begin = Tk.Begin;
Tk.SkipUntil(';');
// Mark the full range as complex
MarkIdentifiersAsComplexBase(FParseViewType(Begin, Tk.Begin - Begin));
#if UE_SHADER_SDCE_LOG_COMPLEX
UE_LOG(LogTemp, Error, TEXT("Skipped complex statement: %hs"), *FParseStringType::ConstructFromPtrSize(Begin, Tk.Begin - Begin));
#endif // UE_SHADER_SDCE_LOG_COMPLEX
}
}
void MarkIdentifiersAsComplexBase(const FParseViewType& Region)
{
// Setup tokenizer
FShallowTokenizer Tk;
Tk.Begin = Region.Begin;
Tk.End = Region.Begin + Region.Length;
Tk.Next();
// Mark all identifiers as complex
while (!Tk.IsEOS())
{
if (Tk.Is(ETokenType::ID))
{
OpBuffer.Add(FVisitOp { .Type = EVisitOp::ComplexID, .ID = FSmallStringView::Get(Tk.Token) });
}
Tk.Next();
}
Tk.OpHead = TokenizerInvalidOpHead;
}
bool TryParseFunction(FShallowTokenizer& TkOuter)
{
FShallowTokenizer Tk = Branch(TkOuter);
SkipTemplate(Tk);
SkipAnnotations(Tk);
FToken ReturnType;
if (!TryParseType(Tk, ReturnType))
{
Reject(Tk);
return false;
}
if (!TryParsePrimary(Tk, false))
{
Reject(Tk);
return false;
}
if (!Tk.IsConsume(ETokenType::ParenthesisOpen))
{
Reject(Tk);
return false;
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FunctionPrologue });
// Parse all parameters
while (!Tk.IsEOS() && !Tk.Is(ETokenType::ParenthesisClose))
{
SkipQualifiers(Tk);
WithStatement(Tk, [&] { TryParseDeclaration(Tk, false); });
if (!Tk.IsConsume(ETokenType::Comma))
{
break;
}
}
if (!Tk.IsConsume(ETokenType::ParenthesisClose))
{
// TODO: Maybe this is less of a matching failure, and more of a chunk failure?
Reject(Tk);
return false;
}
if (Tk.Is(ETokenType::BodyOpen))
{
ParseBody(Tk);
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FunctionEpilogue });
Accept(TkOuter, Tk);
return true;
}
bool TryParseDeclaration(FShallowTokenizer& TkOuter, bool bAllowMultiDeclarators = true)
{
FShallowTokenizer Tk = Branch(TkOuter);
SkipTemplate(Tk);
SkipQualifiers(Tk);
int32 TypeStart = OpBuffer.Num();
FToken Type;
if (!TryParseType(Tk, Type))
{
Reject(Tk);
return false;
}
int32 TypeEnd = OpBuffer.Num();
// May have multiple declarators "int a, b, ..."
for (;;)
{
int32 DrainAnchor = OpBuffer.Num();
// Push type ops again if secondary
if (OpBuffer.Num() != TypeEnd)
{
for (int i = TypeStart; i < TypeEnd; ++i)
{
OpBuffer.Add(OpBuffer[i]);
}
}
FToken ID = Tk.Next();
if (ID.Type != ETokenType::ID)
{
Reject(Tk);
return false;
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::ID, .ID = FSmallStringView::Get(ID) });
// Declarators may have type postfixes after the ID
TryParseTypePostfix(Tk);
// Don't care about bindings
SkipDeclarationBindings(Tk);
OpBuffer.Add(FVisitOp { .Type = EVisitOp::Declaration });
// Handle inline assignments
if (Tk.Is(ETokenType::BinaryEq))
{
OpBuffer.Add(FVisitOp { .Type = EVisitOp::Binary, .Binary = { Tk.Token } });
Tk.Next();
if (!TryParseExpression(Tk, false))
{
Reject(Tk);
return false;
}
}
if (!bAllowMultiDeclarators || !Tk.IsConsume(ETokenType::Comma))
{
break;
}
// Last value carries over
PushDrain(DrainAnchor, TEXT("MultiDeclarator"));
}
Accept(TkOuter, Tk);
return true;
}
bool TryParseType(FShallowTokenizer& Tk, FToken& Type)
{
// Note: This is obviously an over simplified type
// When needed, it'll be expanded on
Type = Tk.Next();
if (Type.Type != ETokenType::ID)
{
return false;
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::Type, .ID = { FSmallStringView::Get(Type) } });
TryParseTypePostfix(Tk);
if (TemplatedTypes.Contains(FSmallStringView::Get(Type)))
{
SkipTemplateSpecialization(Tk);
}
return true;
}
void TryParseTypePostfix(FShallowTokenizer& Tk)
{
for (;;)
{
if (Tk.IsConsume(ETokenType::SquareOpen))
{
// May close immediately for automatic sizing
if (!Tk.Is(ETokenType::SquareClose))
{
int32 DrainAnchor = OpBuffer.Num();
TryParseExpression(Tk);
PushDrain(DrainAnchor, TEXT("TypeArrayDim"));
}
check(Tk.Is(ETokenType::SquareClose));
Tk.Next();
continue;
}
// Not a postfix
break;
}
}
bool IsUnaryPrimaryValueModifier(ETokenType Type)
{
switch (Type)
{
default:
return false;
case ETokenType::BinaryAdd:
case ETokenType::BinarySub:
case ETokenType::UnaryBitNegate:
case ETokenType::UnaryNot:
case ETokenType::UnaryInc:
case ETokenType::UnaryDec:
return true;
}
}
bool ParseParenthesis(FShallowTokenizer& Tk)
{
Tk.Next();
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FramePrologue, .bPrivate = false });
int32 DrainAnchor = OpBuffer.Num();
TryParseExpression(Tk);
// Expecting close
if (!Tk.IsConsume(ETokenType::ParenthesisClose))
{
return false;
}
// Cast?
if (Tk.Is(ETokenType::ID) || Tk.Is(ETokenType::Numeric) || IsUnaryPrimaryValueModifier(Tk.Token.Type))
{
PushDrain(DrainAnchor, TEXT("Cast"));
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FrameEpilogue, .bPrivate = false });
return TryParsePrimary(Tk);
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FrameEpilogue, .bPrivate = false });
// Otherwise, allow accessors
TryParsePrimaryAccessors(Tk);
return true;
}
void ParseTernary(FShallowTokenizer& Tk, bool bAllowExpressionCarry = true)
{
check(Tk.Is(ETokenType::Ternary));
Tk.Next();
// Pass value
{
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FramePrologue, .bPrivate = false });
int32 DrainAnchor = OpBuffer.Num();
TryParseExpression(Tk, bAllowExpressionCarry);
PushDrain(DrainAnchor, TEXT("TenaryPass"));
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FrameEpilogue, .bPrivate = false });
}
check(Tk.Is(ETokenType::Colon));
Tk.Next();
// Failure value
{
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FramePrologue, .bPrivate = false });
int32 DrainAnchor = OpBuffer.Num();
TryParseExpression(Tk, bAllowExpressionCarry);
PushDrain(DrainAnchor, TEXT("TernaryFail"));
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FrameEpilogue, .bPrivate = false });
}
}
bool TryParseExpression(FShallowTokenizer& TkOuter, bool bAllowExpressionCarry = true)
{
FShallowTokenizer Tk = Branch(TkOuter);
int32 DrainAnchor = OpBuffer.Num();
if (!TryParsePrimary(Tk))
{
Reject(Tk);
return false;
}
if (!TryParseBinary(Tk))
{
Reject(Tk);
return false;
}
if (Tk.Is(ETokenType::Ternary))
{
ParseTernary(Tk, bAllowExpressionCarry);
}
if (bAllowExpressionCarry && Tk.IsConsume(ETokenType::Comma))
{
PushDrain(DrainAnchor, TEXT("CommaCarry"));
if (!TryParseExpression(Tk))
{
Reject(Tk);
return false;
}
}
Accept(TkOuter, Tk);
return true;
}
bool ParseUnary(FShallowTokenizer& Tk)
{
Tk.Next();
return TryParsePrimary(Tk);
}
bool ParseAggregateInitializer(FShallowTokenizer& Tk)
{
Tk.Next();
while (!Tk.IsEOS() && !Tk.Is(ETokenType::BodyClose))
{
int32 DrainAnchor = OpBuffer.Num();
if (!TryParseExpression(Tk, false))
{
return false;
}
PushDrain(DrainAnchor, TEXT("AggregateInitMember"));
if (!Tk.IsConsume(ETokenType::Comma))
{
break;
}
}
// Zero for now, until we need it
OpBuffer.Add(FVisitOp { .Type = EVisitOp::AggregateComposite });
return Tk.IsConsume(ETokenType::BodyClose);
}
bool TryParsePrimary(FShallowTokenizer& TkOuter, bool bAllowCall = true)
{
if (TkOuter.Is(ETokenType::ParenthesisOpen))
{
return ParseParenthesis(TkOuter);
}
if (IsUnaryPrimaryValueModifier(TkOuter.Token.Type))
{
return ParseUnary(TkOuter);
}
if (TkOuter.Is(ETokenType::BodyOpen))
{
return ParseAggregateInitializer(TkOuter);
}
FShallowTokenizer Tk = Branch(TkOuter);
FToken PrimID = Tk.Next();
if (PrimID.Type != ETokenType::ID && PrimID.Type != ETokenType::Numeric)
{
Reject(Tk);
return false;
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::ID, .ID = { FSmallStringView::Get(PrimID) } });
if (!TryParsePrimaryAccessors(Tk, bAllowCall))
{
Reject(Tk);
return false;
}
Accept(TkOuter, Tk);
return true;
}
void PushDrain(int32 Anchor, const TCHAR* Reason)
{
// TODO: Ignore drains on statement ends
EPrecedence Precedence = EPrecedence::None;
// Zero op range? early out
if (Anchor == OpBuffer.Num())
{
return;
}
// Current number of private frames
int32 PrivateFrameCounter = 0;
// We need to maintain the precedence of the value that we're draining,
// otherwise we violate the op "hierarchy" (it's flat, but the implicit one).
for (int32 i = Anchor; i < OpBuffer.Num(); i++)
{
EPrecedence VisitPrecedence;
// Get precedence for the op
const FVisitOp& Op = OpBuffer[i];
switch (Op.Type)
{
default:
{
checkNoEntry();
continue;
}
case EVisitOp::ID:
{
VisitPrecedence = EPrecedence::Primary;
break;
}
case EVisitOp::Type:
{
VisitPrecedence = EPrecedence::Primary;
break;
}
case EVisitOp::Binary:
{
VisitPrecedence = Op.Binary.Token.Type == ETokenType::BinaryEq ? EPrecedence::BinaryAssign : EPrecedence::BinaryGeneric;
break;
}
case EVisitOp::MemberAccess: {
VisitPrecedence = EPrecedence::Access;
break;
}
case EVisitOp::Declaration:
{
VisitPrecedence = EPrecedence::Declare;
break;
}
case EVisitOp::AggregateComposite:
{
VisitPrecedence = EPrecedence::Primary;
break;
}
case EVisitOp::FramePrologue:
{
if (Op.bPrivate)
{
PrivateFrameCounter++;
}
continue;
}
case EVisitOp::FrameEpilogue:
{
if (Op.bPrivate)
{
PrivateFrameCounter--;
}
continue;
}
case EVisitOp::StatementPrologue:
case EVisitOp::StatementEpilogue:
case EVisitOp::StructPrologue:
case EVisitOp::StructEpilogue:
case EVisitOp::FunctionPrologue:
case EVisitOp::FunctionEpilogue:
case EVisitOp::Alias:
case EVisitOp::Drain:
{
VisitPrecedence = EPrecedence::None;
break;
}
}
// If we're in a private frame, do not account for it
if (PrivateFrameCounter > 0)
{
continue;
}
Precedence = FMath::Max(Precedence, VisitPrecedence);
}
// Must close out
check(PrivateFrameCounter == 0);
// Must have found at least one op
check(Precedence != EPrecedence::None);
OpBuffer.Add(FVisitOp { .Type = EVisitOp::Drain, .Drain = { Reason, Precedence } });
}
bool TryParsePrimaryAccessors(FShallowTokenizer& Tk, bool bAllowCall = true)
{
for (;;)
{
// ++/--
if (Tk.Is(ETokenType::UnaryInc) || Tk.Is(ETokenType::UnaryDec))
{
Tk.Next();
continue;
}
// Array accessor
if (Tk.Is(ETokenType::SquareOpen))
{
Tk.Next();
int32 DrainAnchor = OpBuffer.Num();
if (!TryParseExpression(Tk))
{
return false;
}
PushDrain(DrainAnchor, TEXT("ArrayAccessor"));
check(Tk.Is(ETokenType::SquareClose));
Tk.Next();
continue;
}
// Member access
if (Tk.Is(ETokenType::Access) || Tk.Is(ETokenType::NamespaceAccess))
{
Tk.Next();
FToken ID = Tk.Next();
if (ID.Type != ETokenType::ID)
{
return false;
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::MemberAccess, .ID = { FSmallStringView::Get(ID) } });
continue;
}
// Calls
if (bAllowCall && Tk.Is(ETokenType::ParenthesisOpen))
{
Tk.Next();
while (!Tk.IsEOS() && !Tk.Is(ETokenType::ParenthesisClose))
{
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FramePrologue, .bPrivate = true });
int32 DrainAnchor = OpBuffer.Num();
if (!TryParseExpression(Tk, false))
{
return false;
}
PushDrain(DrainAnchor, TEXT("CallArg"));
OpBuffer.Add(FVisitOp { .Type = EVisitOp::FrameEpilogue, .bPrivate = true });
if (!Tk.IsConsume(ETokenType::Comma))
{
break;
}
}
// Unexpected closure
if (!Tk.Is(ETokenType::ParenthesisClose))
{
return false;
}
Tk.Next();
continue;
}
// Not an accessor
break;
}
// OK
return true;
}
bool TryParseBinary(FShallowTokenizer& Tk)
{
// Note: Other than assignments, we are effectively not caring about precedence
while (!Tk.IsEOS())
{
// Is this a binary operation?
switch (Tk.Token.Type)
{
default:
// OK
return true;
case ETokenType::BinaryEq:
case ETokenType::BinaryNotEq:
case ETokenType::BinaryPlusEq:
case ETokenType::BinarySubEq:
case ETokenType::BinaryDivEq:
case ETokenType::BinaryMulEq:
case ETokenType::BinaryModEq:
case ETokenType::BinaryXEq:
case ETokenType::BinaryOrEq:
case ETokenType::BinaryAndEq:
case ETokenType::BinaryShlEq:
case ETokenType::BinaryShrEq:
case ETokenType::LogicalAnd:
case ETokenType::LogicalOr:
case ETokenType::BinaryLess:
case ETokenType::BinaryLessEq:
case ETokenType::BinaryGreater:
case ETokenType::BinaryGreaterEq:
case ETokenType::BinaryLogicalEq:
case ETokenType::BinaryAdd:
case ETokenType::BinarySub:
case ETokenType::BinaryDiv:
case ETokenType::BinaryMul:
case ETokenType::BinaryMod:
case ETokenType::BinaryXor:
case ETokenType::BinaryBitOr:
case ETokenType::BinaryBitAnd:
case ETokenType::BinaryShl:
case ETokenType::BinaryShr:
break;
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::Binary, .Binary = { Tk.Token } });
Tk.Next();
if (!TryParsePrimary(Tk))
{
return false;
}
}
return true;
}
bool TryParseUsingAlias(FShallowTokenizer& Tk)
{
check(Tk.Is(ETokenType::Using));
Tk.Next();
FToken ID = Tk.Next();
if (ID.Type != ETokenType::ID)
{
return false;
}
if (!Tk.IsConsume(ETokenType::BinaryEq))
{
return false;
}
FToken Type;
if (!TryParseType(Tk, Type))
{
return false;
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::Alias });
return true;
}
bool TryParseTypedefAlias(FShallowTokenizer& Tk)
{
check(Tk.Is(ETokenType::Typedef));
Tk.Next();
FToken Type;
if (!TryParseType(Tk, Type))
{
return false;
}
FToken ID = Tk.Next();
if (ID.Type != ETokenType::ID)
{
return false;
}
OpBuffer.Add(FVisitOp { .Type = EVisitOp::Alias });
return true;
}
void SkipAnnotations(FShallowTokenizer& Tk)
{
while (Tk.Is(ETokenType::SquareOpen))
{
// TODO: Structural skipping!
Tk.SkipUntil(TEXT(']'));
Tk.Next();
}
}
bool SkipTemplate(FShallowTokenizer& Tk)
{
if (!Tk.IsConsume(ETokenType::Template))
{
return false;
}
if (!Tk.Is(ETokenType::BinaryGreater))
{
// TODO: Structural skipping!
Tk.SkipUntil(TEXT('>'));
}
Tk.Next();
return true;
}
void SkipTemplateSpecialization(FShallowTokenizer& Tk)
{
if (!Tk.IsConsume(ETokenType::BinaryLess))
{
return;
}
if (!Tk.Is(ETokenType::BinaryGreater))
{
// TODO: Structural skipping!
Tk.SkipUntil(TEXT('>'));
}
Tk.Next();
}
void SkipDeclarationBindings(FShallowTokenizer& Tk)
{
if (!Tk.IsConsume(ETokenType::Colon))
{
return;
}
// If this is a binding, make an educated guess
if (!Tk.Is(ETokenType::Comma) && !Tk.Is(ETokenType::EndOfStatement) && !Tk.Is(ETokenType::ParenthesisClose))
{
Tk.SkipUntil(TEXT(",;"));
}
}
void SkipQualifiers(FShallowTokenizer& Tk)
{
for (;;)
{
switch (Tk.Token.Type)
{
default:
return;
case ETokenType::Static:
case ETokenType::Const:
case ETokenType::In:
case ETokenType::Out:
case ETokenType::Inout:
case ETokenType::NoInterp:
case ETokenType::NoInterpolation:
case ETokenType::NoPerspective:
case ETokenType::GroupShared:
case ETokenType::Triangle:
case ETokenType::Centroid:
case ETokenType::Vertices:
case ETokenType::Indices:
case ETokenType::Primitives:
case ETokenType::Uniform:
Tk.Next();
break;
}
}
}
private:
FShallowTokenizer Branch(const FShallowTokenizer& Tk)
{
FShallowTokenizer Branch = Tk.Branch();
Branch.OpHead = OpBuffer.Num();
return MoveTemp(Branch);
}
void Reject(FShallowTokenizer& Tk)
{
OpBuffer.SetNumNoAlloc(Tk.OpHead);
Tk.OpHead = TokenizerInvalidOpHead;
}
void Accept(FShallowTokenizer& TkOuter, FShallowTokenizer& Tk)
{
TkOuter.Accept(Tk);
}
private:
enum class EVisitOp
{
ID,
ComplexID,
Type,
Binary,
MemberAccess,
AggregateComposite,
Declaration,
StatementPrologue,
StatementEpilogue,
StructPrologue,
StructEpilogue,
FunctionPrologue,
FunctionEpilogue,
FramePrologue,
FrameEpilogue,
Alias,
Drain
};
struct FVisitOp
{
EVisitOp Type;
/** Payload */
union
{
/** EVisitOp::ID/ComplexID */
FSmallStringView ID;
/** EVisitOp::FramePrologue/FrameEpilogue */
bool bPrivate;
/** EVisitOp::StructPrologue */
struct
{
FSmallStringView ID;
} Struct;
/** EVisitOp::Binary */
struct
{
FToken Token;
} Binary;
/** EVisitOp::Drain */
struct
{
const TCHAR* Reason;
EPrecedence Type;
} Drain;
/** EVisitOp::StatementPrologue/StatementEpilogue */
struct
{
FParseCharType* Cursor;
} Statement;
};
};
static_assert(sizeof(FVisitOp) == 24, "Unexpected FVisitOp size");
/** Current visitation buffer */
TOpBuffer<FVisitOp, 256> OpBuffer;
private:
/** Tracked templated types */
TSet<FConstSmallStringView, FConstSmallStringKeyFuncs, FMemStackSetAllocator> TemplatedTypes;
};
}