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

2335 lines
87 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "uLang/Common/Common.h"
#include "uLang/Common/Text/Unicode.h"
#include "uLang/Common/Text/StringUtils.h"
#include "uLang/Common/Misc/EnumUtils.h"
#include "uLang/Common/Text/FilePathUtils.h"
#include "uLang/Common/Text/UTF8StringBuilder.h"
#include "uLang/Common/Text/VerseStringEscaping.h"
#include "uLang/Syntax/VstNode.h"
#include "uLang/Syntax/vsyntax_types.h" // Alter for new parser?
#include "uLang/Semantics/Expression.h"
#include "uLang/Diagnostics/Glitch.h"
namespace Verse
{
struct PrettyPrintVisitor
{
PrettyPrintVisitor(uLang::CUTF8StringBuilder& out_string, const int32_t InitialIndent = 0)
: os(out_string)
, indent_amount(InitialIndent)
, PrettyFlags(EPrettyPrintBehaviour::Default)
, bNewlinePending(false)
, bSpacingNewlinePending(false)
{
do_indent();
}
PrettyPrintVisitor(uLang::CUTF8StringBuilder& out_string, const EPrettyPrintBehaviour PrettyFlags, const int32_t InitialIndent = 0)
: os(out_string)
, indent_amount(InitialIndent)
, PrettyFlags(PrettyFlags)
, bNewlinePending(false)
, bSpacingNewlinePending(false)
{
do_indent();
}
void PrintCommaSeparatedChildren(const Vst::Node& parent)
{
const int32_t NumChildren = parent.GetChildCount();
for (int32_t ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex)
{
if (ChildIndex)
{
os.Append(',');
}
PrintElement(parent.GetChildren()[ChildIndex]);
}
}
void PrintAuxAfter(const TSPtr<Vst::Clause>& Aux)
{
if (!Aux)
{
return;
}
for (const TSRef<Vst::Node>& CurrentChild : Aux->AccessChildren())
{
// the actual attribute node is wrapped in a dummy Clause (used to preserve comments in the VST)
ULANG_ASSERTF(CurrentChild->IsA<Vst::Clause>(), "attribute nodes are expected to be wrapped in a dummy Clause node with a single child");
ULANG_ASSERTF(CurrentChild->GetChildCount() == 1, "attribute nodes are expected to be wrapped in a dummy Clause node with a single child");
for (const TSRef<Vst::Node>& PreCommentNode : CurrentChild->GetPrefixComments())
{
Vst::Node::VisitWith(PreCommentNode, *this);
}
// NOTE: (yiliang.siew) We force newlines to never occur for aux attributes that appear after since
// there is no good way to break them up for the parser atm.
if (this->bNewlinePending)
{
this->bNewlinePending = false;
}
if (this->bSpacingNewlinePending)
{
this->bSpacingNewlinePending = false;
}
// TODO: (yiliang.siew)
//CurrentChild->GetChildren()[0]->SetNewLineAfter(false);
os.Append('<');
PrintElement(CurrentChild->GetChildren()[0]);
os.Append('>');
for (const TSRef<Vst::Node>& PostCommentNode : CurrentChild->GetPostfixComments())
{
Vst::Node::VisitWith(PostCommentNode, *this);
}
}
}
void PrintNodeNewLinesBefore(const TSRef<Vst::Node>& InNode)
{
if (InNode->HasNewLinesBefore())
{
for (int32_t Index = 0; Index < InNode->NumNewLinesBefore(); ++Index)
{
os.Append('\n');
}
do_indent();
bNewlinePending = false;
}
}
void PrintNodeNewLinesAfter(const TSRef<Vst::Node>& InNode)
{
if (bNewlinePending && InNode->HasNewLineAfter())
{
for (int32_t Index = 0; Index < InNode->NumNewLinesAfter(); ++Index)
{
os.Append('\n');
}
bNewlinePending = false;
}
}
void PrintElement(const TSRef<Vst::Node>& InNode)
{
PrintNodeNewLinesBefore(InNode);
// NOTE: (yiliang.siew) We indent when needed, which means after there is a line break.
// This simplifies trying to indent when printing newlines after the current node, because
// there are multiple callsites that invoke `PrintElement` for lists of expressions, and
// we don't want to generate needless indentation.
if (os.LastByte() == '\n')
{
do_indent();
}
const TSPtr<Vst::Clause>& Aux = InNode->GetAux();
bool bPrintAuxAfter = InNode->IsA<Vst::Identifier>() || InNode->IsA<Vst::PrePostCall>();
bool bPrintAnyAux = Aux.IsValid() && !InNode->IsA<Vst::Mutation>();
bool bCanHaveLineBreak = true;
if (InNode->HasParent())
{
const Vst::Node* Parent = InNode->GetParent();
// If the current node is the return type in a function, we cannot place it on a newline;
// the parser doesn't support this syntax.
if (Parent->IsA<Vst::TypeSpec>() && Parent->GetChildren().IndexOfByKey(InNode) == 1)
{
bCanHaveLineBreak = false;
}
}
if (bSpacingNewlinePending && bCanHaveLineBreak)
{
// TODO: (YiLiangSiew) All instances that assume adding a newline as just an LF character is wrong.
// This _has_ to take existing line endings into account. And indentation as well.
os.Append('\n');
do_indent();
bSpacingNewlinePending = false;
}
if (bPrintAnyAux && !bPrintAuxAfter)
{
for (const TSRef<Vst::Node>& CurrentChild : Aux->AccessChildren())
{
if (bNewlinePending)
{
bNewlinePending = false;
os.Append('\n');
do_indent();
}
// Because each attribute will have a newline after itself, we take into account if a attribute
// was just printed with a newline, and add the necessary indentation.
else if (os.LastByte() == '\n')
{
do_indent();
}
// the actual attribute node is wrapped in a dummy Clause (used to preserve comments in the VST)
ULANG_ASSERTF(CurrentChild->IsA<Vst::Clause>(), "attribute nodes are expected to be wrapped in a dummy Clause node with a single child");
ULANG_ASSERTF(CurrentChild->GetChildCount() == 1, "attribute nodes are expected to be wrapped in a dummy Clause node with a single child");
for (const TSRef<Vst::Node>& PreCommentNode : CurrentChild->GetPrefixComments())
{
Vst::Node::VisitWith(PreCommentNode, *this);
}
os.Append('@');
const TSRef<Vst::Node> AttributeNode = CurrentChild->GetChildren()[0];
PrintElement(AttributeNode);
// NOTE: (yiliang.siew) Force spaces between attributes or newlines, depending on the context.
if (!AttributeNode->HasNewLineAfter()
// If there is already a newline, do not add a space. This newline could have been printed by a postfix comment.
&& bCanHaveLineBreak && !HasTrailingNewLine(this->os))
{
os.Append(' ');
}
for (const TSRef<Vst::Node>& PostCommentNode : CurrentChild->GetPostfixComments())
{
Vst::Node::VisitWith(PostCommentNode, *this);
}
}
}
// NOTE: (yiliang.siew) We print the prefix comments here because if a node has prepend attributes,
// they need to be printed _before_ the prefix comments for the current node.
// Except in the special case of qualified identifiers; we defer printing them because we want to print
// the preceding expression, then the prefix comments of the identifier, and then the identifier itself.
// The printing of such prefix comments is handled in the `visit` overload for identifiers.
const bool bIsQualifiedIdentifier = InNode->IsA<Vst::Identifier>() && InNode->GetChildCount() != 0;
if (!bIsQualifiedIdentifier)
{
for (const TSRef<Vst::Node>& PreCommentNode : InNode->GetPrefixComments())
{
PrintElement(PreCommentNode);
}
}
if (bNewlinePending)
{
os.Append('\n');
do_indent();
bNewlinePending = false;
}
// NOTE: (yiliang.siew) This is a really stupid HACK, but to account for cases where indentation _is_
// needed, but no newline is actually requested, this check makes sure that the pretty-printer doesn't
// forget to do the indentation when needed but also not add extraneous newlines in the process.
else if (os.LastByte() == '\n' && indent_amount > 0)
{
do_indent();
}
Vst::Node::VisitWith(InNode, *this);
if (bPrintAnyAux && bPrintAuxAfter)
{
PrintAuxAfter(Aux);
}
for (const TSRef<Vst::Node>& PostCommentNode : InNode->GetPostfixComments())
{
PrintElement(PostCommentNode);
}
bNewlinePending = bNewlinePending || InNode->HasNewLineAfter();
PrintNodeNewLinesAfter(InNode);
}
void VisitExpressionList(const Vst::NodeArray& Expressions, const CUTF8StringView& Separator)
{
const int32_t NumExpressions = Expressions.Num();
for (int32_t Idx = 0; Idx < NumExpressions; ++Idx)
{
const TSRef<Vst::Node>& Expression = Expressions[Idx];
ULANG_ASSERT(Expression.IsValid());
PrintElement(Expression);
bool bCommentFollowsCurrentComment = false;
if (Expression->IsA<Vst::Comment>() && Idx < NumExpressions - 1)
{
const TSRef<Vst::Node>& NextExpression = Expressions[Idx + 1];
if (NextExpression.IsValid() && NextExpression->IsA<Vst::Comment>())
{
bCommentFollowsCurrentComment = true;
}
}
// If there are already trailing newlines between expressions, we do not need an additional separator.
if (!bNewlinePending && Idx != Expressions.Num() - 1 && CountNumTrailingNewLines(os) == 0 &&
// Block comments do not need separators between them.
!bCommentFollowsCurrentComment)
{
os.Append(Separator);
}
}
}
void VisitClause(const TSRef<Vst::Clause>& Node, const CUTF8StringView& Separator)
{
VisitExpressionList(Node->GetChildren(), Separator);
}
void VisitBinaryOp(const TSRef<Vst::Node>& Operand1, const char* OperandCstr, const TSRef<Vst::Node>& Operand2)
{
// NOTE: (yiliang.siew) Because for syntax such as:
//
// ```
// f():void=
// return 5
// ```
//
// We do not want the printing of the `f():void` typespec to print a newline before printing the `=` operand,
// if we notice that the typespec expression has a newline after, we manually remove it and print that newline here
// _after_ printing the operand. However, it is forced to be limited to 1 newline, as any other value would be invalid.
if (Operand1->HasNewLineAfter())
{
ULANG_ENSUREF(Operand1->NumNewLinesAfter() == 1, "A typespec definition had more than 1 newline set after it, which would result in an invalid parse; this was forced to a single newline instead!");
Operand1->SetNewLineAfter(false); // So that we don't print a newline before the operand in this pretty-printing.
PrintElement(Operand1);
Operand1->SetNewLineAfter(true);
bNewlinePending = true;
}
else
{
PrintElement(Operand1);
}
os.Append(OperandCstr);
int32_t SavedIndentAmount = indent_amount;
const bool bIsRhsIndentedBlock = bNewlinePending;
if (bNewlinePending)
{
os.Append('\n');
++indent_amount;
do_indent();
bNewlinePending = false;
}
if (Operand2->GetElementType() == Vst::NodeType::Clause)
{
const TSRef<Vst::Clause>& RhsClause = Operand2.As<Vst::Clause>();
if (bIsRhsIndentedBlock)
{
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPrefixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
VisitClause(RhsClause, "");
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPostfixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
}
else if (RhsClause->GetForm() == Vst::Clause::EForm::NoSemicolonOrNewline)
{
if (RhsClause->GetChildCount() == 0)
{
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPrefixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
os.Append('{');
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPostfixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
os.Append('}');
}
else if (RhsClause->GetChildCount() == 1)
{
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPrefixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
const Vst::Clause::EPunctuation RhsClausePunctuation = RhsClause->GetPunctuation();
if (RhsClausePunctuation == Vst::Clause::EPunctuation::Braces)
{
os.Append('{');
VisitClause(RhsClause, ", ");
os.Append('}');
}
else
{
VisitClause(RhsClause, ", ");
}
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPostfixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
}
else
{
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPrefixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
os.Append('{');
VisitClause(RhsClause, ", ");
os.Append('}');
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPostfixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
}
}
else
{
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPrefixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
os.Append('{');
VisitClause(RhsClause, "; ");
os.Append(";}");
for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPostfixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
// // NOTE: (yiliang.siew) Because we don't know what this clause will be in terms of its formatting
// // whether using semicolons only, or newlines only, or a mixture. Ideally we need to store trailing
// // semicolon information per-VST node as well and pretty-print that to roundtrip accurately.
// // This is a stopgap measure; if all the nodes in the clause have newlines, then we'll infer that curly
// // braces are not required.
// bool bUsingCurlyBraces = false;
// for (const TSRef<Vst::Node>& Node : RhsClause->GetChildren())
// {
// if (!Node->HasNewLineAfter())
// {
// bUsingCurlyBraces = true;
// break;
// }
// }
// if (!bUsingCurlyBraces)
// {
// // NOTE: (yiliang.siew) We need to manually add a newline after the `=` token.
// os.Append('\n');
// ++indent_amount;
// do_indent();
// VisitClause(RhsClause, "");
// --indent_amount;
// }
// else
// {
// os.Append('{');
// VisitClause(RhsClause, "; ");
// os.Append(';');
// }
// for (const TSRef<Vst::Node>& CommentNode : RhsClause->GetPostfixComments())
// {
// Vst::Node::VisitWith(CommentNode, *this);
// }
// if (bUsingCurlyBraces)
// {
// os.Append('}');
// }
}
}
else
{
PrintElement(Operand2);
}
indent_amount = SavedIndentAmount;
}
void visit(const Vst::Comment& node)
{
if (this->bNewlinePending)
{
os.Append("\n");
this->bNewlinePending = false;
}
if (node._Type == Vst::Comment::EType::block)
{
os.Append(node.GetSourceText());
}
else if(node._Type == Vst::Comment::EType::line)
{
os.Append(node.GetSourceText());
bNewlinePending = true;
}
else if (node._Type == Vst::Comment::EType::ind)
{
os.Append(node.GetSourceText());
bNewlinePending = true;
}
else if (node._Type == Vst::Comment::EType::frag)
{
os.Append(node.GetSourceText());
bNewlinePending = true;
}
}
void visit(const Vst::Project& node)
{
for (const TSRef<Vst::Node>& child : node.GetChildren())
{
PrintElement(child);
}
}
void visit(const Vst::Package& node)
{
for (const TSRef<Vst::Node>& child : node.GetChildren())
{
PrintElement(child);
}
}
void visit(const Vst::Module& node)
{
for (const TSRef<Vst::Node>& child : node.GetChildren())
{
PrintElement(child);
}
}
void visit(const Vst::Snippet& node)
{
VisitExpressionList(node.GetChildren(), node.GetForm() == Vst::Clause::EForm::NoSemicolonOrNewline ? ", " : "");
}
void visit(const Vst::PrefixOpLogicalNot& node)
{
os.Append("not");
const TSRef<Vst::Node>& Operand = node.GetInnerNode();
if (node.Whence().EndRow() < Operand->Whence().BeginRow())
{
os.Append('\n');
++indent_amount;
do_indent();
Vst::Node::VisitWith(Operand, *this);
--indent_amount;
}
else
{
os.Append(' ');
Vst::Node::VisitWith(Operand, *this);
}
}
void visit(const Vst::Definition& Node)
{
const TSRef<Vst::Node>& LeftOperand = Node.GetOperandLeft();
const char* OpCStr;
if (LeftOperand->IsA<Vst::TypeSpec>())
{
// `x:t := v` can be simplified to `x:t = v`
// TODO: (yiliang.siew) This needs to not have the spaces baked in since the roundtripping tests
// are also specialized to that as well.
OpCStr = " = ";
}
else
{
OpCStr = " := ";
}
const TSRef<Vst::Node>& RightOperand = Node.GetOperandRight();
VisitBinaryOp(LeftOperand, OpCStr, RightOperand);
// Adding a newline between declarations
// Search RHS child[0][0][0][0]... for a newline
bool bEncounteredNewline = LeftOperand->HasNewLineAfter();
auto CurrentNode = RightOperand;
while (CurrentNode || !bEncounteredNewline)
{
if (CurrentNode->HasNewLineAfter())
{
bEncounteredNewline = true;
break;
}
if (CurrentNode->AccessChildren().Num() > 0)
{
CurrentNode = CurrentNode->AccessChildren()[0];
}
else
{
CurrentNode.Reset();
break;
}
}
if (bEncounteredNewline)
{
bSpacingNewlinePending = true;
}
}
void visit(const Vst::Assignment& Node)
{
using EOp = Vst::Assignment::EOp;
const char* OpCStr = " UnknownOp";
switch (Node.GetOperandRight()->GetTag<EOp>())
{
case EOp::assign: OpCStr = " = "; break;
case EOp::addAssign: OpCStr = " += "; break;
case EOp::subAssign: OpCStr = " -= "; break;
case EOp::mulAssign: OpCStr = " *= "; break;
case EOp::divAssign: OpCStr = " /= "; break;
default: ULANG_ENSUREF(false, "Unknown assignment operator!"); break;
}
VisitBinaryOp(Node.GetOperandLeft(), OpCStr, Node.GetOperandRight());
}
void visit(const Vst::BinaryOpCompare& node)
{
const char* OpCStr = " UnknownOp";
switch (node.GetOp())
{
case Vst::BinaryOpCompare::op::lt: OpCStr = " < "; break;
case Vst::BinaryOpCompare::op::lteq: OpCStr = " <= "; break;
case Vst::BinaryOpCompare::op::gt: OpCStr = " > "; break;
case Vst::BinaryOpCompare::op::gteq: OpCStr = " >= "; break;
case Vst::BinaryOpCompare::op::eq: OpCStr = " = "; break;
case Vst::BinaryOpCompare::op::noteq: OpCStr = " <> "; break;
default: ULANG_ENSUREF(false, "Unknown compare operator!"); break;
}
VisitBinaryOp(node.GetOperandLeft(), OpCStr, node.GetOperandRight());
}
void visit(const Vst::BinaryOpLogicalOr& node)
{
const auto& children = node.GetChildren();
const auto num_children = children.Num();
if (num_children > 1)
{
// Note, we are forcing all uses of 'and' in the context of an 'or' to be parenthesized
//TODO: (jcotton) parentheses being forced is causing if conditions to be borderline non-functional to edit in VV, disabled until a better solution is decided on for VV
// e.g. (a and b) or c # those parentheses are mandatory
//const int32_t OpAnd_Precedence_NotATypo = GetOperatorPrecedence(Vst::NodeType::BinaryOpLogicalAnd);
//const bool bFirstEltNeedsParen = children[0]->GetPrecedence() <= OpAnd_Precedence_NotATypo;
//if (bFirstEltNeedsParen) os.Append('(');
PrintElement(children[0]);
//if (bFirstEltNeedsParen) os.Append(')');
for (int32_t i = 1; i < num_children; ++i)
{
os.Append(" or ");
//const bool bNeedParensDueToChildAfterOperator = children[i]->GetPrecedence() <= OpAnd_Precedence_NotATypo;
//if (bNeedParensDueToChildAfterOperator) os.Append('(');
PrintElement(children[i]);
//if (bNeedParensDueToChildAfterOperator) os.Append(')');
}
}
else if (num_children == 1)
{
ULANG_ERRORF("LogicalOperatorOr has just one child; how did that happen?");
PrintElement(children[0]);
}
else
{
ULANG_ERRORF("LogicalOperatorOr has no child nodes; why does it even exist?.");
}
}
void visit(const Vst::BinaryOpLogicalAnd& node)
{
const auto& children = node.GetChildren();
const auto num_children = children.Num();
if (num_children > 1)
{
//const int32_t OpAnd_Precedence = GetOperatorPrecedence(Vst::NodeType::BinaryOpLogicalAnd);
//const bool bFirstEltNeedsParen = children[0]->GetPrecedence() <= OpAnd_Precedence;
//if (bFirstEltNeedsParen) os.Append('(');
PrintElement(children[0]);
//if (bFirstEltNeedsParen) os.Append(')');
for (int32_t i = 1; i < num_children; ++i)
{
os.Append(" and ");
//const bool bNeedParensDueToChildAfterOperator = children[i]->GetPrecedence() <= OpAnd_Precedence;
//if (bNeedParensDueToChildAfterOperator) os.Append('(');
PrintElement(children[i]);
//if (bNeedParensDueToChildAfterOperator) os.Append(')');
}
}
else if (num_children == 1)
{
ULANG_ERRORF("LogicalOperatorAnd has just one child; how did that happen?");
PrintElement(children[0]);
}
else
{
ULANG_ERRORF("LogicalOperatorAnd has no child nodes; why does it even exist?.");
}
}
void visit(const Vst::BinaryOp& node)
{
const auto& children = node.GetChildren();
const auto num_children = children.Num();
if (num_children > 1)
{
Vst::NodeType NodeType = node.GetElementType();
const int32_t Op_Precedence = GetOperatorPrecedence(NodeType);
const bool bFirstEltNeedsParen = children[0]->GetPrecedence() <= Op_Precedence;
const bool bHasPrefix = children[0]->GetElementType() == Vst::NodeType::Operator;
if (bHasPrefix)
{
os.Append(children[0]->As<Vst::Operator>().GetSourceText());
}
else
{
if (bFirstEltNeedsParen) os.Append('(');
PrintElement(children[0]);
if (bFirstEltNeedsParen) os.Append(')');
}
for (int32_t i = 1; i < num_children; ++i)
{
auto& Operator = children[i];
if (Operator->GetElementType() == Vst::NodeType::Operator)
{
os.Append(' ');
os.Append(Operator->As<Vst::Operator>().GetSourceText());
os.Append(' ');
}
else
{
PrintElement(Operator);
}
// print the operand
i += 1; // move to next child
if (node.IsA<Vst::BinaryOpMulDivInfix>())
{
ULANG_ENSUREF(node.IsA<Vst::BinaryOpAddSub>() || i < num_children, "Malformed binary mul/div node -- missing trailing operand.");
}
if (i < num_children)
{
const bool bNeedParensDueToChildAfterOperator = children[i]->GetPrecedence() <= Op_Precedence;
if (bNeedParensDueToChildAfterOperator) os.Append('(');
auto& TrailingNode = children[i];
if (TrailingNode->GetElementType() == Vst::NodeType::Operator)
{
os.Append(' ');
os.Append(TrailingNode->As<Vst::Operator>().GetSourceText());
os.Append(' ');
}
else
{
PrintElement(TrailingNode);
}
if (bNeedParensDueToChildAfterOperator) os.Append(')');
}
}
}
else if (num_children == 1)
{
ULANG_ERRORF("BinaryOp has just one child; how did that happen?");
PrintElement(children[0]);
}
else
{
ULANG_ERRORF("BinaryOp has no child nodes; why does it even exist?.");
}
}
void visit(const Vst::BinaryOpRange& node)
{
const auto& children = node.GetChildren();
if (children.Num() == 2)
{
const int32_t Range_Precedence = GetOperatorPrecedence(Vst::NodeType::BinaryOpRange);
const bool bFirstEltNeedsParen = children[0]->GetPrecedence() <= Range_Precedence;
if (bFirstEltNeedsParen) os.Append('(');
PrintElement(children[0]);
if (bFirstEltNeedsParen) os.Append(')');
os.Append("..");
const bool bSecondEltNeedsParen = children[1]->GetPrecedence() <= Range_Precedence;
if (bSecondEltNeedsParen) os.Append('(');
PrintElement(children[1]);
if (bSecondEltNeedsParen) os.Append(')');
}
else
{
ULANG_ERRORF("BinaryOpRange must have exactly two children.");
}
}
void visit(const Vst::BinaryOpArrow& node)
{
const auto& children = node.GetChildren();
if (children.Num() == 2)
{
const int32_t ArrowPrecedence = GetOperatorPrecedence(Vst::NodeType::BinaryOpArrow);
const bool bFirstEltNeedsParen = children[0]->GetPrecedence() <= ArrowPrecedence;
if (bFirstEltNeedsParen) os.Append('(');
PrintElement(children[0]);
if (bFirstEltNeedsParen) os.Append(')');
os.Append("->");
const bool bSecondEltNeedsParen = children[1]->GetPrecedence() <= ArrowPrecedence;
if (bSecondEltNeedsParen) os.Append('(');
PrintElement(children[1]);
if (bSecondEltNeedsParen) os.Append(')');
}
else
{
ULANG_ERRORF("BinaryOpArrow must have exactly two children.");
}
}
void visit(const Vst::Where& Node)
{
if (Node.GetChildCount() < 1)
{
ULANG_ERRORF("Where must have at least one child.");
}
PrintElement(Node.GetLhs());
os.Append(" where");
Vst::Where::RhsView Rhs = Node.GetRhs();
if (Rhs.IsEmpty())
{
return;
}
os.Append(' ');
auto I = Rhs.begin();
PrintElement(*I);
++I;
for (auto Last = Rhs.end(); I != Last; ++I)
{
os.Append(", ");
PrintElement(*I);
}
}
void visit(const Vst::Mutation& Node)
{
if (Node.GetChildCount() != 1)
{
ULANG_ERRORF("Var must have one child.");
}
switch (Node._Keyword)
{
case Vst::Mutation::EKeyword::Var:
os.Append("var");
PrintAuxAfter(Node.GetAux());
os.Append(' ');
break;
case Vst::Mutation::EKeyword::Set:
os.Append("set ");
break;
default:
ULANG_UNREACHABLE();
}
PrintElement(Node.Child());
}
void visit(const Vst::TypeSpec& node)
{
if (node.GetChildCount() == 2)
{
const auto& Lhs = node.GetLhs();
const auto& Rhs = node.GetRhs();
const bool LhsNeedsParens = Lhs->GetPrecedence() <= GetOperatorPrecedence(Vst::NodeType::TypeSpec);
const bool RhsNeedsParens = Rhs->GetPrecedence() <= GetOperatorPrecedence(Vst::NodeType::TypeSpec);
if (LhsNeedsParens) os.Append('(');
PrintElement(Lhs);
if (LhsNeedsParens) os.Append(')');
os.Append(':');
for (const TSRef<Vst::Node>& CommentNode : node._TypeSpecComments)
{
Vst::Node::VisitWith(CommentNode, *this);
}
if (RhsNeedsParens) os.Append('(');
PrintElement(Rhs);
if (RhsNeedsParens) os.Append(')');
}
else if (node.GetChildCount() == 1)
{
os.Append(':');
for (const TSRef<Vst::Node>& CommentNode : node._TypeSpecComments)
{
Vst::Node::VisitWith(CommentNode, *this);
}
const auto& Type = node.GetChildren()[0];
PrintElement(Type);
}
else
{
ULANG_ERRORF("TypeSpec must have either one or two children.");
}
}
void visit(const Vst::FlowIf& node)
{
// if any children have a trailing newline we are vertical form.
struct CheckChildrenForNewLine
{
static bool Do(const Vst::NodeArray& NodeArray)
{
for (auto& curNode : NodeArray)
{
if (curNode->HasNewLineAfter())
{
return true;
}
if (Do(curNode->AccessChildren()))
{
return true;
}
}
return false;
}
};
using EOp = Vst::FlowIf::ClauseTag;
const Vst::NodeArray& NodeChildren = node.GetChildren();
const int32_t NumChildren = node.GetChildCount();
bool bIsVerticalForm = CheckChildrenForNewLine::Do(NodeChildren);
bool bCStyleIf = (NumChildren >= 1) && (NodeChildren[1]->As<Vst::Clause>().GetChildCount() == 1);
for (int32_t idx = 0; idx < NumChildren; idx += 1)
{
const auto& IfClause = NodeChildren[idx]->As<Vst::Clause>();
const bool bIsFirstEntry = (idx == 0);
const bool bMoreThanOneChild = IfClause.GetChildCount() > 1;
const bool bNoChildren = IfClause.GetChildCount() == 0;
const bool bOneChild = IfClause.GetChildCount() == 1;
const bool bSingleChildIsComment = bOneChild && IfClause.GetChildren()[0]->IsA< Vst::Comment>();
bool bUseBraces = false;
bool bDoIndent = false; // indent after tag
// TODO: (yiliang.siew) Maybe not needed?
if (bIsVerticalForm && (idx != 0) && (IfClause.GetTag<EOp>() != EOp::condition) && (IfClause.GetTag<EOp>() != EOp::then_body || !bCStyleIf))
{
if (os.LastByte() != '\n')
{
os.Append('\n');
}
// NOTE: (YiLiangSiew) If we already added a newline, doesn't matter which node prior has a pending newline anymore;
// it's no longer relevant. This way, we avoid "doubling up" on newline formattting.
this->bNewlinePending = false;
do_indent();
}
if (IfClause.GetTag<EOp>() == EOp::if_identifier)
{
if (!bIsFirstEntry)
{
if (bIsVerticalForm && !bCStyleIf)
{
bNewlinePending = true;
}
else
{
bUseBraces = true;
}
}
for (const TSRef<Vst::Node>& CommentNode : IfClause.GetPrefixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
if(!bIsFirstEntry)
{
os.Append("else ");
}
os.Append("if");
for (const TSRef<Vst::Node>& CommentNode : IfClause.GetPostfixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
}
if (IfClause.GetTag<EOp>() == EOp::condition)
{
// For printing an if with a conditional, but no body
if (NumChildren == 1 && bOneChild)
{
os.Append(":");
bIsVerticalForm = true;
bUseBraces = false;
bDoIndent = true;
}
else if (bIsVerticalForm)
{
if (bCStyleIf)
{
os.Append(" ");
bDoIndent = false;
bUseBraces = true;
}
else
{
bNewlinePending = true;
bDoIndent = true;
os.Append(':');
}
}
else
{
bUseBraces = true;
}
}
if (IfClause.GetTag<EOp>() == EOp::then_body)
{
if (bIsVerticalForm)
{
if (bCStyleIf)
{
bDoIndent = true;
}
else
{
os.Append("then:");
bNewlinePending = true;
bDoIndent = true;
}
}
else
{
os.Append("then ");
}
}
if (IfClause.GetTag<EOp>() == EOp::else_body)
{
if (bIsVerticalForm)
{
bNewlinePending = true;
}
for (const TSRef<Vst::Node>& CurComment : IfClause.GetPrefixComments())
{
Vst::Node::VisitWith(CurComment, *this);
}
os.Append("else");
if (bIsVerticalForm)
{
os.Append(':');
}
else
{
os.Append(" ");
}
if (bCStyleIf)
{
if (bIsVerticalForm)
{
bDoIndent = true;
}
}
else
{
if (bIsVerticalForm)
bDoIndent = true;
}
}
const int32_t ConditionalChildCount = IfClause.GetChildCount();
bool bWasIndented = false;
if (bIsVerticalForm && bDoIndent)
{
// TODO: (yiliang.siew) Maybe not required?
++indent_amount;
bWasIndented = true;
if (bCStyleIf)
{
bNewlinePending = true;
}
}
if (IfClause.GetTag<EOp>() != EOp::if_identifier)
{
// NOTE: (yiliang.siew) We can skip this for `else` clauses because we already printed it above.
if (IfClause.GetTag<EOp>() != EOp::else_body)
{
for (const TSRef<Vst::Node>& CommentNode : IfClause.GetPrefixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
}
if (bUseBraces)
os.Append("(");
else if ((bMoreThanOneChild || bNoChildren || bSingleChildIsComment) && !bIsVerticalForm)
os.Append("{");
}
for (int32_t ConditionalIdx = 0; ConditionalIdx < ConditionalChildCount; ConditionalIdx += 1)
{
if (!bIsVerticalForm)
{
bNewlinePending = false;
}
PrintElement(IfClause.GetChildren()[ConditionalIdx]);
if (bIsVerticalForm)
{
if (!IfClause.GetChildren()[ConditionalIdx]->HasNewLineAfter())
{
if (ConditionalIdx != ConditionalChildCount - 1)
{
os.Append(", ");
}
}
}
else
{
if (ConditionalIdx != ConditionalChildCount - 1)
{
os.Append("; ");
}
}
}
if (IfClause.GetTag<EOp>() != EOp::if_identifier)
{
if (bUseBraces)
{
os.Append(")");
}
else if ((bMoreThanOneChild || bNoChildren || bSingleChildIsComment) && !bIsVerticalForm)
{
os.Append("}");
}
for (const TSRef<Vst::Node>& CommentNode : IfClause.GetPostfixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
if (bCStyleIf && IfClause.GetTag<EOp>() == EOp::condition && bIsVerticalForm && NumChildren != 1)
{
os.Append(':');
}
// TODO: (yiliang.siew) Maybe not required anymore?
if (bWasIndented){ --indent_amount; }
if (!bIsVerticalForm && idx != NumChildren-1) { os.Append(' '); }
}
}
}
void VisitPrePostCall(const Vst::PrePostCall& node, int32_t First, int32_t Last)
{
for (int i = First; i <= Last; i += 1)
{
const auto& child = node.GetChildren()[i];
using Op = Verse::Vst::PrePostCall::Op;
auto thisOp = child->GetTag<Op>();
bool bPrintPostComments = true;
if (thisOp == Op::Expression)
{
PrintElement(child);
bPrintPostComments = false;
}
else if (thisOp == Op::DotIdentifier)
{
if (i > First)
{
os.Append('.');
}
PrintElement(child);
bPrintPostComments = false;
}
else if (thisOp == Op::FailCall || thisOp == Op::SureCall)
{
os.Append(thisOp == Op::SureCall ? "(" : "[");
for (const TSRef<Vst::Node>& CommentNode : child->GetPrefixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
VisitClause(child.As<Vst::Clause>(), ", ");
for (const TSRef<Vst::Node>& CommentNode : child->GetPostfixComments())
{
Vst::Node::VisitWith(CommentNode, *this);
}
os.Append(thisOp == Op::SureCall ? ")" : "]");
bPrintPostComments = false;
}
else if (thisOp == Op::Pointer)
{
os.Append('^');
}
else if (thisOp == Op::Option)
{
os.Append('?');
}
if (bPrintPostComments)
{
for (const TSRef<Vst::Node>& PostCommentNode : child->GetPostfixComments())
{
Vst::Node::VisitWith(PostCommentNode, *this);
}
}
}
}
void visit(const Vst::PrePostCall& node)
{
const int32_t NumChildren = node.GetChildCount();
VisitPrePostCall(node, 0, NumChildren - 1);
}
void visit(const Vst::Identifier& node)
{
if (node.GetChildCount())
{
for (const TSRef<Vst::Node>& CurComment : node._QualifierPreComments)
{
Vst::Node::VisitWith(CurComment, *this);
}
os.Append('(');
PrintCommaSeparatedChildren(node);
os.Append(":)");
for (const TSRef<Vst::Node>& CurComment : node.GetPrefixComments())
{
Vst::Node::VisitWith(CurComment, *this);
}
for (const TSRef<Vst::Node>& PostCommentNode : node._QualifierPostComments)
{
Vst::Node::VisitWith(PostCommentNode, *this);
}
}
os.Append(node.GetStringValue());
}
void visit(const Vst::Operator& node)
{
os.Append(node.GetStringValue());
}
void visit(const Vst::IntLiteral& node)
{
os.Append(node.GetStringValue());
}
void visit(const Vst::FloatLiteral& node)
{
os.Append(node.GetStringValue());
}
void visit(const Vst::CharLiteral& node)
{
os.Append("\'");
os.Append(node.GetStringValue());
os.Append("\'");
}
void visit(const Vst::StringLiteral& node)
{
os.Append("\"");
os.Append(uLang::VerseStringEscaping::EscapeString(node.GetStringValue()));
os.Append("\"");
}
void visit(const Vst::PathLiteral& node)
{
os.Append(node.GetStringValue());
}
void PrintClause(const Vst::Clause& Clause)
{
if (Clause.GetChildCount() == 0
|| Clause.GetForm() == Vst::Clause::EForm::NoSemicolonOrNewline)
{
const bool bNeedsBraces = Clause.GetChildCount() != 1;
if (bNeedsBraces)
{
os.Append('{');
}
PrintCommaSeparatedChildren(Clause);
if (bNeedsBraces)
{
os.Append('}');
}
}
else
{
const bool bVerticalForm = bNewlinePending || Clause.GetPunctuation() == Vst::Clause::EPunctuation::Indentation;
if (!bVerticalForm)
{
os.Append('{');
}
++indent_amount;
const int32_t NumChildren = Clause.GetChildCount();
for (int32_t ChildIndex = 0; ChildIndex < NumChildren; ChildIndex += 1)
{
const TSRef<Vst::Node>& Child = Clause.GetChildren()[ChildIndex];
PrintElement(Child);
// NOTE: (yiliang.siew) Do not add a semicolon to the start of an expression which already has a newline in front of it.
if (!bNewlinePending && ChildIndex + 1 < NumChildren && !HasTrailingNewLine(os))
{
os.Append(';');
}
}
--indent_amount;
if (!bVerticalForm)
{
os.Append('}');
}
}
}
void visit(const Vst::Interpolant& node)
{
ULANG_ERRORF("Unexpected Interpolant node");
}
void visit(const Vst::InterpolatedString& node)
{
os.Append("\"");
for (const TSRef<Vst::Node>& Child : node.GetChildren())
{
if (const Vst::StringLiteral* StringLiteral = Child->AsNullable<Vst::StringLiteral>())
{
os.Append(uLang::VerseStringEscaping::EscapeString(StringLiteral->GetStringValue()));
}
else if (const Vst::Interpolant* Interpolant = Child->AsNullable<Vst::Interpolant>())
{
os.Append("{");
PrintClause(Interpolant->GetChildren()[0]->As<Vst::Clause>());
os.Append("}");
}
else
{
ULANG_ERRORF("Unexpected InterpolatedString VST node child %s", GetNodeTypeName(Child->GetElementType()));
}
}
os.Append("\"");
}
void visit(const Vst::Lambda& node)
{
const int32_t NumChildren = node.GetChildCount();
if (ULANG_ENSUREF(NumChildren >= 2, "Lambda must have at least 2 children"))
{
PrintElement(node.GetDomain());
// NOTE: (yiliang.siew) We take into account if the lambda clause has a leading newline
// for its first member, and manipulate the pretty-printer into printing it here properly.
if (node.GetChildCount() > 1 && node.GetChildren()[1]->IsA<Vst::Clause>())
{
const Vst::Clause& TheClause = node.GetChildren()[1]->As<Vst::Clause>();
if (TheClause.GetChildCount() > 0 && TheClause.GetChildren()[0]->HasNewLinesBefore())
{
bNewlinePending = true;
}
}
os.Append(bNewlinePending ? " =>" : " => ");
PrintClause(*node.GetRange());
}
}
void visit(const Vst::Control& node)
{
bool bPrintReturnExpression = false;
switch (node._Keyword)
{
case Vst::Control::EKeyword::Return:
os.Append("return");
bPrintReturnExpression = true;
break;
case Vst::Control::EKeyword::Break:
os.Append("break");
break;
case Vst::Control::EKeyword::Yield:
os.Append("yield");
break;
case Vst::Control::EKeyword::Continue:
os.Append("continue");
break;
default:
ULANG_UNREACHABLE();
}
if (node.GetChildCount() == 0)
{
bNewlinePending = true;
return;
}
if (bPrintReturnExpression)
{
const TSRef<Vst::Node>& ReturnExpr = node.GetReturnExpression();
if (ReturnExpr.IsValid())
{
// Append a space after the `return` token.
os.Append(' ');
}
PrintElement(ReturnExpr);
}
}
void visit(const Vst::Macro& node)
{
PrintElement(node.GetName());
ULANG_ENSUREF(node.GetChildCount() > 1, "Malformed macro");
const TSRef<Vst::Identifier>& LeftChild = node.GetChildren()[0].As<Vst::Identifier>();
TSPtr<Vst::Clause> SecondChild;
if (node.GetChildCount() > 1)
{
SecondChild = node.GetChildren()[1].As<Vst::Clause>();
}
bool bIsVerticalForm = false;
for (int ChildIndex = 1; ChildIndex < node.GetChildCount(); ++ChildIndex)
{
const TSRef<Vst::Clause>& Child = node.GetChildren()[ChildIndex].As<Vst::Clause>();
if ((SecondChild && SecondChild->HasNewLineAfter() && SecondChild->GetChildCount() > 0 && // If the clause has no children, there's no point giving it a vertical form.
ChildIndex == node.GetChildCount() - 1) ||
LeftChild->HasNewLineAfter() || // If there is a newline after, it has to be a vertical form.
Child->HasNewLineAfter())
{
bIsVerticalForm = true;
}
else
{
bIsVerticalForm = false;
}
const auto Keyword = Child->GetTag<vsyntax::res_t>();
const bool bUseRoundBrackets = Keyword == vsyntax::res_of && ChildIndex == 1;
if (bUseRoundBrackets)
{
os.Append("(");
}
else
{
if (bIsVerticalForm)
{
os.Append(':');
bNewlinePending = true;
}
else
{
os.Append(" ");
}
os.Append(vsyntax::scan_reserved_t()[Keyword]);
if (!bIsVerticalForm && Keyword != '\0')
os.Append(" ");
if (!bIsVerticalForm)
os.Append("{");
}
if (bIsVerticalForm )
{
++indent_amount;
}
const int32_t NumDescendants = Child->GetChildCount();
for (int DescendantIndex = 0; DescendantIndex < NumDescendants; ++DescendantIndex)
{
const uLang::TSRef<Vst::Node> CurChild = Child->GetChildren()[DescendantIndex];
PrintElement(CurChild);
// Always print the newlines regardless of whether it is the final element, since
// that best respects the attribute set on the VST node.
bool bIndentationMaybeNeeded = false;
if (bNewlinePending && CurChild->HasNewLineAfter())
{
for (int32_t Index = 0; Index < CurChild->NumNewLinesAfter(); ++Index)
{
os.Append('\n');
}
bIndentationMaybeNeeded = true;
bNewlinePending = false;
}
if (DescendantIndex + 1 < NumDescendants)
{
// If we are not the last node, we can indent for the next node to be printed. Otherwise
// this would just create unneeded indentation.
if (bIndentationMaybeNeeded)
{
do_indent();
bIndentationMaybeNeeded = false;
}
VERSE_SUPPRESS_UNUSED(PrettyFlags);
// We do not add commas after comments since it actually changes their text.
// We also do not add commas if there is already a trailing newline separator or a newline about to be printed.
if (!bNewlinePending && !CurChild->HasNewLineAfter() && !CurChild->IsA<Vst::Comment>() && !HasTrailingNewLine(this->os))
{
os.Append(", ");
}
}
}
if (bUseRoundBrackets)
{
os.Append(')');
// This could occur in a macro's 2nd clause (i.e. `C := class(D):` where `(D)` is considered a vertical form clause that increases the indentation level.)
if (bIsVerticalForm)
{
--indent_amount;
}
}
else
{
if (bIsVerticalForm)
{
--indent_amount;
}
else
{
os.Append('}');
}
}
for (const TSRef<Vst::Node>& PostCommentNode : Child->GetPostfixComments())
{
Vst::Node::VisitWith(PostCommentNode, *this);
}
// Account for clauses that have newlines after themselves set, even if their children do not.
// NOTE: (yiliang.siew) We print the comments first before setting this attribute so that trailing
// block comments do not have newlines inserted _before_ they get printed.
if (Child->HasNewLineAfter())
{
PrintNodeNewLinesAfter(Child);
}
}
}
/// This is only necessary since we declare the `VISIT_VSTNODE` macro for all VST node types.
void visit(const Vst::Clause& node)
{
ULANG_ENSUREF(false, "A clause means nothing without the context of its parent, the parent is responsible for serializing it");
}
void visit(const Vst::Parens& node)
{
os.Append('(');
int32_t ChildCount = node.GetChildCount();
if (ChildCount)
{
PrintElement(node.GetChildren()[0]);
for (int32_t i = 1; i < ChildCount; ++i)
{
os.Append(", ");
PrintElement(node.GetChildren()[i]);
}
}
os.Append(')');
}
void visit(const Vst::Commas& node)
{
int32_t ChildCount = node.GetChildCount();
if (ChildCount)
{
PrintElement(node.GetChildren()[0]);
for (int32_t i = 1; i < ChildCount; ++i)
{
os.Append(", ");
PrintElement(node.GetChildren()[i]);
}
}
}
void visit(const Vst::Placeholder& Placeholder)
{
// TODO: (YiLiangSiew) This has to take line endings into account.
if (this->bNewlinePending)
{
os.Append("\n");
this->bNewlinePending = false;
}
os.Append("stub{");
os.Append(Placeholder.GetSourceText());
os.Append("}");
}
void visit(const Vst::ParseError& Error)
{
os.AppendFormat("Error (%d:%d) : %s", Error.Whence().BeginRow(), Error.Whence().BeginColumn(), Error.GetError());
}
void visit(const Vst::Escape& Escape)
{
os.Append('&');
if (Escape.GetChildCount() == 1)
{
PrintElement(Escape.GetChildren()[0]);
}
}
private:
static constexpr char IndentationString[] = " ";
void do_indent()
{
for (int i = 0; i < indent_amount; ++i)
{
os.Append(IndentationString);
}
}
uLang::CUTF8StringBuilder& os;
int32_t indent_amount;
EPrettyPrintBehaviour PrettyFlags;
bool bNewlinePending;
bool bSpacingNewlinePending;
}; // PrettyPrintVisitor
void VstAsCodeSourceAppend(const TSRef<Vst::Node>& VstNode, uLang::CUTF8StringBuilder& Source)
{
PrettyPrintVisitor prettyPrinter(Source);
Vst::Node::VisitWith(VstNode, prettyPrinter);
}
void VstAsCodeSourceAppend(const TSRef<Vst::Node>& VstNode, const EPrettyPrintBehaviour Flags, uLang::CUTF8StringBuilder& Source)
{
PrettyPrintVisitor prettyPrinter(Source, Flags);
Vst::Node::VisitWith(VstNode, prettyPrinter);
}
void VstAsCodeSourceAppend(const TSRef<Vst::PrePostCall>& VstNode, uLang::CUTF8StringBuilder& Source, int32_t First, int32_t Last)
{
PrettyPrintVisitor prettyPrinter(Source);
prettyPrinter.VisitPrePostCall(*VstNode.Get(), First, Last);
}
void VstAsCodeSourceAppend(const TSRef<Vst::Clause>& VstClause, CUTF8StringBuilder& Source, int32_t InitialIndent, CUTF8String const& Separator)
{
PrettyPrintVisitor prettyPrinter(Source, InitialIndent);
for (const TSRef<Vst::Node>& CommentNode : VstClause->GetPrefixComments())
{
Vst::Node::VisitWith(CommentNode, prettyPrinter);
}
prettyPrinter.VisitClause(VstClause, Separator);
for (const TSRef<Vst::Node>& CommentNode : VstClause->GetPostfixComments())
{
Vst::Node::VisitWith(CommentNode, prettyPrinter);
}
}
bool GeneratePathToPostfixComment(const TSRef<Vst::Node>& Target, const TSRef<Vst::Node>& Node, int32_t& CommentIndex)
{
for (int idx = 0; idx < Node->GetPostfixComments().Num(); idx++)
{
if (Node->GetPostfixComments()[idx] == Target)
{
CommentIndex = idx;
return true;
}
}
return false;
}
bool GeneratePathToPrefixComment(const TSRef<Vst::Node>& Target, const TSRef<Vst::Node>& Node, int32_t& CommentIndex)
{
for (int idx = 0; idx < Node->GetPrefixComments().Num(); idx++)
{
if (Node->GetPrefixComments()[idx] == Target)
{
CommentIndex = idx;
return true;
}
}
return false;
}
bool GeneratePathToAuxNode(const TSRef<Vst::Node>& Target, const TSRef<Vst::Node>& Node, LArray<int32_t>& AuxPath)
{
//special case where the Aux node is what were looking for
if (Target == Node)
{
AuxPath.Add(-1);
return true;
}
for (int idx = 0; idx < Node->GetChildCount(); idx++)
{
TSRef<Vst::Node> Child = Node->GetChildren()[idx];
if (Child == Target || GeneratePathToAuxNode(Target, Child, AuxPath))
{
AuxPath.Add(idx);
return true;
}
}
return false;
}
bool GeneratePathToNode_Internal(const TSRef<Vst::Node>& Node, const Vst::NodeArray& Snippet, SPathToNode& PathToNode)
{
for (int idx = 0; idx < Snippet.Num(); idx++)
{
TSRef<Vst::Node> Child = Snippet[idx];
if (Child == Node
|| GeneratePathToNode_Internal(Node, Child->GetChildren(), PathToNode)
|| (Child->GetAux() && GeneratePathToAuxNode(Node, Child->GetAux().AsRef(), PathToNode.AuxPath))
|| GeneratePathToPrefixComment(Node, Child, PathToNode.PreCommentIndex)
|| GeneratePathToPostfixComment(Node, Child, PathToNode.PostCommentIndex))
{
PathToNode.Path.Add(idx);
return true;
}
}
return false;
}
bool GeneratePathToNode(const TSRef<Vst::Node>& Node, const TSRef<Vst::Snippet>& VstSnippet, SPathToNode& PathToNode)
{
PathToNode.Path.Empty();
PathToNode.AuxPath.Empty();
PathToNode.PostCommentIndex = -1;
PathToNode.PreCommentIndex = -1;
return GeneratePathToNode_Internal(Node, VstSnippet->GetChildren(), PathToNode);
}
// Returns null if path does not return a node
TSPtr<Vst::Node> GetNodeFromPath(const TSRef<Vst::Snippet>& VstSnippet, const SPathToNode& PathData, bool bReturnParent)
{
if (PathData.Path.IsEmpty())
{
return nullptr;
}
TSPtr<Vst::Node> CurrNode = VstSnippet;
for (int idx = PathData.Path.Num() - 1; idx >= 0; idx--)
{
if (!(bReturnParent && idx == 0 && PathData.AuxPath.IsEmpty()) && CurrNode->GetChildren().IsValidIndex(PathData.Path[idx]))
{
CurrNode = CurrNode->GetChildren()[PathData.Path[idx]];
}
}
if (!PathData.AuxPath.IsEmpty() && CurrNode && CurrNode->GetAux())
{
CurrNode = CurrNode->GetAux();
if (PathData.AuxPath[0] == -1)
{
return CurrNode;
}
for (int idx = PathData.AuxPath.Num() - 1; idx >= 0; idx--)
{
if (!(bReturnParent && idx == 0) && CurrNode->GetChildren().IsValidIndex(PathData.AuxPath[idx]))
{
CurrNode = CurrNode->GetChildren()[PathData.AuxPath[idx]];
}
}
}
if (CurrNode->GetPostfixComments().IsValidIndex(PathData.PostCommentIndex) && !bReturnParent)
{
CurrNode = CurrNode->GetPostfixComments()[PathData.PostCommentIndex];
}
if (CurrNode->GetPrefixComments().IsValidIndex(PathData.PreCommentIndex) && !bReturnParent)
{
CurrNode = CurrNode->GetPrefixComments()[PathData.PreCommentIndex];
}
return CurrNode;
}
namespace Vst
{
Node::~Node()
{
Empty();
if (_MappedAstNode)
{
if (ULANG_ENSUREF(_MappedAstNode->_MappedVstNode == this, "Syntax<>Semantic mappings must be reciprocal."))
{
_MappedAstNode->_MappedVstNode = nullptr;
}
}
}
void Node::ReplaceSelfWith(const TSRef<Node>& replacement)
{
ULANG_ASSERTF(_Parent, "Must have parent to be removed from.");
int32_t idx = _Parent->AccessChildren().Find(SharedThis(this));
// @nicka, @sree : seems like we could use an "IsOperator()" functionality here? or "CaresAboutTag()?"
if (idx >= 1 && (
_Parent->GetElementType() == NodeType::BinaryOpCompare
|| _Parent->GetElementType() == NodeType::BinaryOpAddSub
|| _Parent->GetElementType() == NodeType::BinaryOpMulDivInfix))
{
replacement->SetTag(GetTag<uint8_t>());
}
_Parent->AccessChildren().RemoveAt(idx);
_Parent->AppendChildAt(replacement, idx);
replacement->_Parent = _Parent;
_Parent = nullptr;
DebugOrphanCheck();
}
bool Node::RemoveFromParent(int32_t idx)
{
if (!ULANG_ENSUREF(_Parent, "Must have parent to be removed from."))
{
return false;
}
if (idx == uLang::IndexNone)
{
idx = _Parent->GetChildren().IndexOfByKey(this);
}
if (_Type == NodeType::Comment)
{
if (idx != uLang::IndexNone)
{
_Parent->AccessChildren().RemoveAt(idx);
_Parent = nullptr;
return true;
}
idx = _Parent->GetPostfixComments().IndexOfByKey(this);
if (idx != uLang::IndexNone)
{
_Parent->AccessPostfixComments().RemoveAt(idx);
_Parent = nullptr;
return true;
}
idx = _Parent->GetPrefixComments().IndexOfByKey(this);
if (idx != uLang::IndexNone)
{
_Parent->AccessPrefixComments().RemoveAt(idx);
_Parent = nullptr;
return true;
}
return false;
}
else if (_Parent->GetAux() == this)
{
_Parent->_Aux.Reset();
}
else
{
_Parent->AccessChildren().RemoveAt(idx);
_Parent = nullptr;
}
return true;
}
void Node::AddMapping(uLang::CAstNode* AstNode) const
{
// Make previous mapping non reciprocal, if there was one.
if (_MappedAstNode)
{
if (ULANG_ENSUREF(_MappedAstNode->_MappedVstNode == this, "Syntax<>Semantic mappings must be reciprocal."))
{
_MappedAstNode->_VstMappingType = uLang::EVstMappingType::AstNonReciprocal;
}
}
// If there's already a non-reciprocal mapping from the AST node to this VST node, promote it to be reciprocal.
if (AstNode->_VstMappingType == uLang::EVstMappingType::AstNonReciprocal && AstNode->_MappedVstNode == this)
{
_MappedAstNode = AstNode;
AstNode->_VstMappingType = uLang::EVstMappingType::Ast;
}
else if (ULANG_ENSUREF(AstNode->_MappedVstNode == nullptr, "Expression already mapped to an Vst node."))
{
_MappedAstNode = AstNode;
AstNode->_MappedVstNode = this;
}
}
void Node::RemoveMapping(uLang::CAstNode* AstNode)
{
if(AstNode->_MappedVstNode && ULANG_ENSUREF(AstNode->_MappedVstNode->_MappedAstNode == AstNode, "Syntax<>Semantic mappings must be reciprocal."))
{
AstNode->_MappedVstNode->_MappedAstNode = nullptr;
AstNode->_MappedVstNode = nullptr;
}
}
const TSRef<Node> MakeStub(const SLocus& Whence)
{
return TSRef<Placeholder>::New(Whence);
}
bool Node::HasAttributes() const
{
return _Aux && (_Aux->GetChildCount() > 0);
}
const Identifier* Node::GetAttributeIdentifier(const CUTF8StringView& AttributeName) const
{
if (!_Aux)
{
return nullptr;
}
for (const auto& Child : _Aux->GetChildren())
{
// the actual attribute node is wrapped in a dummy Clause (used to preserve comments in the VST)
ULANG_ASSERTF(Child->IsA<Vst::Clause>(), "attribute nodes are expected to be wrapped in a dummy Clause node with a single child");
ULANG_ASSERTF(Child->GetChildCount() == 1, "attribute nodes are expected to be wrapped in a dummy Clause node with a single child");
const Node& Attr = *Child->GetChildren()[0];
if (const Identifier* AttrIdentifier = Attr.AsNullable<Identifier>())
{
if(AttrIdentifier->GetSourceText() == AttributeName)
{
return AttrIdentifier;
}
}
}
return nullptr;
}
bool Node::IsAttributePresent(const CUTF8StringView& AttributeName) const
{
return GetAttributeIdentifier(AttributeName) != nullptr;
}
const Node* Node::TryGetFirstAttributeOfType(NodeType Type) const
{
if (!_Aux)
{
return nullptr;
}
for (const auto& Child : _Aux->GetChildren())
{
// the actual attribute node is wrapped in a dummy Clause (used to preserve comments in the VST)
ULANG_ASSERTF(Child->IsA<Vst::Clause>(), "attribute nodes are expected to be wrapped in a dummy Clause node with a single child");
ULANG_ASSERTF(Child->GetChildCount() == 1, "attribute nodes are expected to be wrapped in a dummy Clause node with a single child");
const Node& Attr = *Child->GetChildren()[0];
if (Attr.GetElementType() == Type)
{
return &Attr;
}
}
return nullptr;
}
void Node::EnsureAuxAllocated()
{
if (!_Aux.IsValid())
{
_Aux = TSPtr<Clause>::New(Whence(), Clause::EForm::Synthetic);
_Aux->_Parent = this;
}
}
void Node::PrependAux(const TSRef<Node>& AuxChild)
{
EnsureAuxAllocated();
_Aux->AppendChildAt(AuxChild, 0);
}
void Node::PrependAux(const NodeArray& AuxChildren)
{
EnsureAuxAllocated();
_Aux->PrependChildren(AuxChildren);
}
void Node::AppendAux(const TSRef<Node>& AuxChild)
{
EnsureAuxAllocated();
_Aux->AppendChild(AuxChild);
}
void Node::AppendAux(const NodeArray& AuxChildren)
{
EnsureAuxAllocated();
_Aux->AppendChildren(AuxChildren);
}
void Node::AppendAuxAt(const TSRef<Node>& AuxChild, int32_t Idx)
{
EnsureAuxAllocated();
_Aux->AppendChildAt(AuxChild, Idx);
}
void Node::SetAux(const TSRef<Clause>& Aux)
{
if (ULANG_ENSUREF(!Aux->GetParent(), "Aux Node already has a parent!"))
{
_Aux = Aux;
_Aux->_Parent = this;
}
}
void Node::AppendPrefixComment(const TSRef<Node>& CommentNode)
{
CommentNode->_Parent = this;
_PreComments.Add(CommentNode);
}
void Node::AppendPrefixComments(const NodeArray& CommentNodes)
{
for(auto& CommentNode : CommentNodes)
{
CommentNode->_Parent = this;
}
_PreComments.Append(CommentNodes);
}
void Node::AppendPostfixComment(const TSRef<Node>& CommentNode)
{
CommentNode->_Parent = this;
_PostComments.Add(CommentNode);
}
void Node::AppendPostfixComments(const NodeArray& CommentNodes)
{
for (auto& CommentNode : CommentNodes)
{
CommentNode->_Parent = this;
}
_PostComments.Append(CommentNodes);
}
const CUTF8String& Node::GetSnippetPath() const
{
if (_Type == NodeType::Package)
{
return As<Package>()._FilePath;
}
if (_Type == NodeType::Module && As<Module>()._FilePath.IsFilled())
{
return As<Module>()._FilePath;
}
const Snippet* MySnippet = GetParentOfType<Snippet>();
return MySnippet ? MySnippet->_Path : CUTF8String::GetEmpty();
}
const Snippet* Node::FindSnippetByFilePath(const CUTF8StringView& FilePath) const
{
// Is it this?
if (_Type == NodeType::Snippet)
{
const Snippet* FoundSnippet = &As<Snippet>();
return uLang::FilePathUtils::NormalizePath(FoundSnippet->_Path) == FilePath ? FoundSnippet : nullptr;
}
// Check children only if project, package or module
if (_Type != NodeType::Project && _Type != NodeType::Package && _Type != NodeType::Module)
{
return nullptr;
}
// Is it any of the children?
for (const TSRef<Vst::Node>& child : _Children)
{
const Snippet* FoundSnippet = child->FindSnippetByFilePath(FilePath);
if (FoundSnippet)
{
return FoundSnippet;
}
}
// Nothing found
return nullptr;
}
const Node* Node::FindChildByPosition(const SPosition& TextPosition) const
{
// Is it any of the children?
for (const TSRef<Vst::Node>& child : _Children)
{
const Node* FoundNode = child->FindChildByPosition(TextPosition);
if (FoundNode)
{
return FoundNode;
}
}
// Is it this?
if (_Whence.IsValid() && _Whence.IsInRange(TextPosition))
{
return this;
}
// Nothing found
return nullptr;
}
const TSRef<Node> Node::FindChildClosestToPosition(const SPosition& TextPosition, const CUTF8StringView& SourceText) const
{
ULANG_ASSERTF(TextPosition.IsValid(), "An invalid text position was passed in as a parameter!");
// NOTE: (yiliang.siew) We DFS the VST and at each traversal, we store the signed distance between the node and
// the text position. At the end, we sort the signed distances in order to find the minimal absolute value.
constexpr uint32_t DefaultArraySize = 64;
LArray<uLang::LocusDistanceResult> AbsDistances;
AbsDistances.Reserve(DefaultArraySize);
LArray<TSRef<Node>> Stack;
Stack.Reserve(DefaultArraySize);
Stack.Add(this->GetSharedSelf());
while (!Stack.IsEmpty())
{
// TODO: (yiliang.siew) Comments/Aux nodes are technically also nodes that could be desired to be found.
// However, because `VerseAssist` doesn't yet handle those node types gracefully, we defer getting their distances here as well.
const TSRef<Node> CurrentNode = Stack.Pop();
// Since we're just finding the closest node regardless of prefix/suffix, we can rely on the absolute distance.
const int32_t AbsDistance = abs(GetSignedDistanceBetweenPositionAndLocus(CurrentNode->Whence(), TextPosition, SourceText));
AbsDistances.Add({CurrentNode, AbsDistance});
Stack.Append(CurrentNode->GetChildren());
}
ULANG_ASSERTF(AbsDistances.Num() > 0, "Invalid traversal of VST encountered!");
uLang::LocusDistanceResult MinDistance = AbsDistances[0];
for (const uLang::LocusDistanceResult& CurDistance : AbsDistances)
{
if (CurDistance.Distance < MinDistance.Distance)
{
MinDistance = CurDistance;
}
}
return MinDistance.Node;
}
const CAtom* Node::AsAtomNullable() const
{
return NodeInfos[static_cast<uint8_t>(GetElementType())].bIsCAtom ? static_cast<const CAtom*>(this) : nullptr;
}
const char* CommentTypeToString(Comment::EType Type)
{
switch (Type)
{
case Comment::EType::block: return "block";
case Comment::EType::line: return "line";
case Comment::EType::ind: return "ind";
case Comment::EType::frag: return "frag";
default: break;
}
ULANG_UNREACHABLE();
return nullptr;
}
TSRef<Module> Package::FindOrAddModule(const CUTF8StringView& ModuleName, const CUTF8StringView& ParentModuleName)
{
uLang::TOptional<TSRef<Module>> FoundModule = FindModule(*this, ModuleName);
if (FoundModule)
{
return *FoundModule;
}
TSRef<Module> NewModule = TSRef<Module>::New(ModuleName);
Node* ModuleContainer = this;
if (!ParentModuleName.IsEmpty())
{
uLang::TOptional<TSRef<Module>> FoundParent = FindModule(*this, ParentModuleName);
if (ULANG_ENSUREF(FoundParent, "Parent module does not exist!"))
{
ModuleContainer = &**FoundParent;
}
}
return ModuleContainer->AppendChild(NewModule).As<Module>();
}
uLang::TOptional<TSRef<Module>> Package::FindModule(const Node& ModuleContainer, const CUTF8StringView& ModuleName)
{
for (const TSRef<Verse::Vst::Node>& Child : ModuleContainer.GetChildren())
{
if (Child->GetElementType() == NodeType::Module)
{
TSRef<Module> FoundModule = Child.As<Module>();
if (FoundModule->_Name == ModuleName)
{
return FoundModule;
}
uLang::TOptional<TSRef<Module>> FoundSubmodule = FindModule(*FoundModule, ModuleName);
if (FoundSubmodule)
{
return *FoundSubmodule;
}
}
}
return EResult::Unspecified;
}
void BinaryOpAddSub::AppendAddOperation(const SLocus& AddWhence, const TSRef<Node>& RhsOperand)
{
AppendOperation_Internal(TSRef<Operator>::New("+", AddWhence), RhsOperand);
}
void BinaryOpAddSub::AppendSubOperation(const SLocus& SubWhence, const TSRef<Node>& RhsOperand)
{
AppendOperation_Internal(TSRef<Operator>::New("-", SubWhence), RhsOperand);
}
void BinaryOpMulDivInfix::AppendMulOperation(const SLocus& MulWhence, const TSRef<Node>& RhsOperand)
{
AppendOperation_Internal(TSRef<Operator>::New("*", MulWhence), RhsOperand);
}
void BinaryOpMulDivInfix::AppendDivOperation(const SLocus& DivWhence, const TSRef<Node>& RhsOperand)
{
AppendOperation_Internal(TSRef<Operator>::New("/", DivWhence), RhsOperand);
}
TSRef<Clause> PrePostCall::PrependQMark(const SLocus& Whence)
{
TSRef<Clause> QMarkClause(TSRef<Clause>::New(static_cast<uint8_t>(Op::Option), Whence, Clause::EForm::Synthetic));
AppendChildAt(QMarkClause, 0);
return QMarkClause;
}
TSRef<Clause> PrePostCall::PrependHat(const SLocus& Whence)
{
TSRef<Clause> HatClause(TSRef<Clause>::New(static_cast<uint8_t>(Op::Pointer), Whence, Clause::EForm::Synthetic));
AppendChildAt(HatClause, 0);
return HatClause;
}
void PrePostCall::PrependCallArgs(bool bCanFail, const TSRef<Clause>& Args)
{
Args->SetTag<Op>(bCanFail ? FailCall : SureCall);
AppendChildAt(Args, 0);
}
void PrePostCall::AppendQMark(const SLocus& Whence)
{
AppendChild(TSRef<Clause>::New(static_cast<uint8_t>(Op::Option), Whence, Clause::EForm::Synthetic));
}
void PrePostCall::AppendHat(const SLocus& Whence)
{
AppendChild(TSRef<Clause>::New(static_cast<uint8_t>(Op::Pointer), Whence, Clause::EForm::Synthetic));
}
void PrePostCall::AppendCallArgs(bool bCanFail, const TSRef<Clause>& Args)
{
Args->SetTag<Op>(bCanFail ? FailCall : SureCall);
AppendChild(Args);
}
void PrePostCall::AppendDotIdent(const SLocus& Whence, const TSRef<Identifier>& Ident)
{
Ident->SetTag<Op>(Op::DotIdentifier);
AppendChild(Ident);
}
TSPtr<Clause> PrePostCall::TakeLastArgs()
{
if (GetChildCount() > 1)
{
const auto Op = GetChildren().Last()->GetTag<PrePostCall::Op>();
if (Op == PrePostCall::SureCall || Op == PrePostCall::FailCall)
{
const auto Args = TakeChildAt(GetChildCount() - 1);
return Args.As<Clause>();
}
}
return TSPtr<Clause>();
}
bool Identifier::AddQualifier(const uLang::CUTF8StringView& InQualifier)
{
if (IsQualified())
{
return false;
}
// TODO: (yiliang.siew) This is a brittle assumption we're making here about the format of a Verse path literal,
// but there doesn't seem to be a better way to ascertain this, nor can we somehow change this during semantic
// analysis later on.
// TODO: (yiliang.siew) Need to figure out how to re-calculate the locus properly now, since all nodes
// in the VST following this change are now affected.
if (InQualifier.FirstByte() == '/')
{
TSRef<PathLiteral> NewPathLiteral = TSRef<PathLiteral>::New(InQualifier, NullWhence());
AppendChild(NewPathLiteral);
}
else
{
TSRef<Identifier> NewQualifier = TSRef<Identifier>::New(InQualifier, NullWhence());
AppendChild(NewQualifier);
}
return true;
}
} // namespace Vst
int32_t GetSignedDistanceBetweenPositionAndLocus(const SLocus& A, const SPosition& B, const CUTF8StringView& SourceText)
{
ULANG_ASSERTF(A.IsValid() && B.IsValid(), "Invalid parameters passed into function!");
ULANG_ASSERTF(!SourceText.IsEmpty(), "Invalid zero-length text was specified!");
if (A.GetBegin() == B || A.GetEnd() == B)
{
return 0;
}
const CUTF8StringView SourceTextA = TextRangeToStringView(SourceText, A);
// If A is not a valid locus for the document, we return a sentinel value by ensuring the distance is as far as possible.
if (SourceTextA.IsEmpty())
{
return INT32_MAX;
}
const uLang::SIdxRange RangeA = SourceText.SubRange(SourceTextA);
uLang::TOptional<int32_t> IndexPositionB = uLang::ScanToRowCol(SourceText, B);
ULANG_ASSERTF(IndexPositionB.IsSet(), "The position provided was not valid for the source text!");
if (A.IsInRange(B))
{
const int32_t DistanceFromStart = IndexPositionB.GetValue() - RangeA._Begin;
const int32_t DistanceFromEnd = RangeA._End - IndexPositionB.GetValue();
return DistanceFromStart < DistanceFromEnd ? DistanceFromStart : -DistanceFromEnd;
}
if (B > A.GetEnd()) // B comes after A
{
return IndexPositionB.GetValue() - RangeA._End;
}
// A comes after B
return (RangeA._Begin - IndexPositionB.GetValue()) * -1;
}
} // namespace Verse