using System.Text; using Lua.CodeAnalysis.Syntax.Nodes; namespace Lua.CodeAnalysis.Syntax; public sealed class DisplayStringSyntaxVisitor : ISyntaxNodeVisitor { public sealed class Context { public readonly ref struct IndentScope { readonly Context source; public IndentScope(Context source) { this.source = source; source.IncreaseIndent(); } public void Dispose() { source.DecreaseIndent(); } } readonly StringBuilder buffer = new(); int indentLevel; bool isNewLine = true; public IndentScope BeginIndentScope() => new(this); public void Append(string value) { if (isNewLine) { buffer.Append(' ', indentLevel * 4); isNewLine = false; } buffer.Append(value); } public void AppendLine(string value) { if (isNewLine) { buffer.Append(' ', indentLevel * 4); isNewLine = false; } buffer.AppendLine(value); isNewLine = true; } public void AppendLine() { buffer.AppendLine(); isNewLine = true; } public override string ToString() => buffer.ToString(); public void IncreaseIndent() { indentLevel++; } public void DecreaseIndent() { if (indentLevel > 0) indentLevel--; } public void Reset() { buffer.Clear(); indentLevel = 0; isNewLine = true; } } readonly Context context = new(); public string GetDisplayString(SyntaxNode node) { context.Reset(); node.Accept(this, context); return context.ToString(); } public bool VisitBinaryExpressionNode(BinaryExpressionNode node, Context context) { node.LeftNode.Accept(this, context); context.Append($" {node.OperatorType.ToDisplayString()} "); node.RightNode.Accept(this, context); return true; } public bool VisitBooleanLiteralNode(BooleanLiteralNode node, Context context) { context.Append(node.Value ? Keywords.True : Keywords.False); return true; } public bool VisitBreakStatementNode(BreakStatementNode node, Context context) { context.Append(Keywords.Break); return true; } public bool VisitCallFunctionExpressionNode(CallFunctionExpressionNode node, Context context) { node.FunctionNode.Accept(this, context); context.Append("("); VisitSyntaxNodes(node.ArgumentNodes, context); context.Append(")"); return true; } public bool VisitCallFunctionStatementNode(CallFunctionStatementNode node, Context context) { node.Expression.Accept(this, context); return true; } public bool VisitDoStatementNode(DoStatementNode node, Context context) { context.AppendLine("do"); using (context.BeginIndentScope()) { foreach (var childNode in node.StatementNodes) { childNode.Accept(this, context); context.AppendLine(); } } context.AppendLine("end"); return true; } public bool VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, Context context) { context.Append("function("); VisitSyntaxNodes(node.ParameterNodes, context); if (node.HasVariableArguments) { if (node.ParameterNodes.Length > 0) context.Append(", "); context.Append("..."); } context.AppendLine(")"); using (context.BeginIndentScope()) { foreach (var childNode in node.Nodes) { childNode.Accept(this, context); context.AppendLine(); } } context.AppendLine("end"); return true; } public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, Context context) { context.Append("function "); context.Append(node.Name.ToString()); context.Append("("); VisitSyntaxNodes(node.ParameterNodes, context); if (node.HasVariableArguments) { if (node.ParameterNodes.Length > 0) context.Append(", "); context.Append("..."); } context.AppendLine(")"); using (context.BeginIndentScope()) { foreach (var childNode in node.Nodes) { childNode.Accept(this, context); context.AppendLine(); } } context.AppendLine("end"); return true; } public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, Context context) { context.Append("function "); for (int i = 0; i < node.MemberPath.Length; i++) { context.Append(node.MemberPath[i].Name.ToString()); if (i == node.MemberPath.Length - 2 && node.HasSelfParameter) { context.Append(":"); } else if (i != node.MemberPath.Length - 1) { context.Append("."); } } context.Append("("); VisitSyntaxNodes(node.ParameterNodes, context); if (node.HasVariableArguments) { if (node.ParameterNodes.Length > 0) context.Append(", "); context.Append("..."); } context.AppendLine(")"); using (context.BeginIndentScope()) { foreach (var childNode in node.Nodes) { childNode.Accept(this, context); context.AppendLine(); } } context.AppendLine("end"); return true; } public bool VisitGenericForStatementNode(GenericForStatementNode node, Context context) { context.Append($"for "); VisitSyntaxNodes(node.Names, context); context.Append(" in "); VisitSyntaxNodes(node.ExpressionNodes, context); context.AppendLine(" do"); using (context.BeginIndentScope()) { foreach (var childNode in node.StatementNodes) { childNode.Accept(this, context); context.AppendLine(); } } context.AppendLine("end"); return true; } public bool VisitGotoStatementNode(GotoStatementNode node, Context context) { context.Append($"goto {node.Name}"); return true; } public bool VisitIdentifierNode(IdentifierNode node, Context context) { context.Append(node.Name.ToString()); return true; } public bool VisitIfStatementNode(IfStatementNode node, Context context) { context.Append("if "); node.IfNode.ConditionNode.Accept(this, context); context.AppendLine(" then"); using (context.BeginIndentScope()) { foreach (var childNode in node.IfNode.ThenNodes) { childNode.Accept(this, context); context.AppendLine(); } } foreach (var elseif in node.ElseIfNodes) { context.Append("elseif "); elseif.ConditionNode.Accept(this, context); context.AppendLine(" then"); using (context.BeginIndentScope()) { foreach (var childNode in elseif.ThenNodes) { childNode.Accept(this, context); context.AppendLine(); } } } if (node.ElseNodes.Length > 0) { context.AppendLine("else"); using (context.BeginIndentScope()) { foreach (var childNode in node.ElseNodes) { childNode.Accept(this, context); context.AppendLine(); } } } context.Append("end"); return true; } public bool VisitLabelStatementNode(LabelStatementNode node, Context context) { context.Append($"::{node.Name}::"); return true; } public bool VisitAssignmentStatementNode(AssignmentStatementNode node, Context context) { VisitSyntaxNodes(node.LeftNodes, context); if (node.RightNodes.Length > 0) { context.Append(" = "); VisitSyntaxNodes(node.RightNodes, context); } return true; } public bool VisitLocalAssignmentStatementNode(LocalAssignmentStatementNode node, Context context) { context.Append("local "); return VisitAssignmentStatementNode(node, context); } public bool VisitLocalFunctionDeclarationStatementNode(LocalFunctionDeclarationStatementNode node, Context context) { context.Append("local "); return VisitFunctionDeclarationStatementNode(node, context); } public bool VisitNilLiteralNode(NilLiteralNode node, Context context) { context.Append(Keywords.Nil); return true; } public bool VisitNumericForStatementNode(NumericForStatementNode node, Context context) { context.Append($"for {node.VariableName} = "); node.InitNode.Accept(this, context); context.Append(", "); node.LimitNode.Accept(this, context); if (node.StepNode != null) { context.Append(", "); node.StepNode.Accept(this, context); } context.AppendLine(" do"); using (context.BeginIndentScope()) { foreach (var childNode in node.StatementNodes) { childNode.Accept(this, context); context.AppendLine(); } } context.AppendLine("end"); return true; } public bool VisitNumericLiteralNode(NumericLiteralNode node, Context context) { context.Append(node.Value.ToString()); return true; } public bool VisitRepeatStatementNode(RepeatStatementNode node, Context context) { context.AppendLine("repeat"); using (context.BeginIndentScope()) { foreach (var childNode in node.Nodes) { childNode.Accept(this, context); context.AppendLine(); } } context.Append("until "); node.ConditionNode.Accept(this, context); context.AppendLine(); return true; } public bool VisitReturnStatementNode(ReturnStatementNode node, Context context) { context.Append("return "); VisitSyntaxNodes(node.Nodes, context); return true; } public bool VisitStringLiteralNode(StringLiteralNode node, Context context) { if (node.IsShortLiteral) { context.Append("\""); context.Append(node.Text.ToString()); context.Append("\""); } else { context.Append("[["); context.Append(node.Text.ToString()); context.Append("]]"); } return true; } public bool VisitSyntaxTree(LuaSyntaxTree node, Context context) { foreach (var statement in node.Nodes) { statement.Accept(this, context); context.AppendLine(); } return true; } public bool VisitTableConstructorExpressionNode(TableConstructorExpressionNode node, Context context) { context.AppendLine("{"); using (context.BeginIndentScope()) { for (int i = 0; i < node.Fields.Length; i++) { var field = node.Fields[i]; switch (field) { case GeneralTableConstructorField general: context.Append("["); general.KeyExpression.Accept(this, context); context.Append("] = "); general.ValueExpression.Accept(this, context); break; case RecordTableConstructorField record: context.Append($"{record.Key} = "); record.ValueExpression.Accept(this, context); break; case ListTableConstructorField list: list.Expression.Accept(this, context); break; } context.AppendLine(i == node.Fields.Length - 1 ? "" : ","); } } context.AppendLine("}"); return true; } public bool VisitTableIndexerAccessExpressionNode(TableIndexerAccessExpressionNode node, Context context) { node.TableNode.Accept(this, context); context.Append("["); node.KeyNode.Accept(this, context); context.Append("]"); return true; } public bool VisitTableMemberAccessExpressionNode(TableMemberAccessExpressionNode node, Context context) { node.TableNode.Accept(this, context); context.Append($".{node.MemberName}"); return true; } public bool VisitCallTableMethodExpressionNode(CallTableMethodExpressionNode node, Context context) { node.TableNode.Accept(this, context); context.Append($":{node.MethodName}("); VisitSyntaxNodes(node.ArgumentNodes, context); context.Append(")"); return true; } public bool VisitCallTableMethodStatementNode(CallTableMethodStatementNode node, Context context) { return node.Expression.Accept(this, context); } public bool VisitUnaryExpressionNode(UnaryExpressionNode node, Context context) { context.Append(node.Operator.ToDisplayString()); if (node.Operator is UnaryOperator.Not) context.Append(" "); node.Node.Accept(this, context); return true; } public bool VisitWhileStatementNode(WhileStatementNode node, Context context) { context.Append("while "); node.ConditionNode.Accept(this, context); context.AppendLine(" do"); using (context.BeginIndentScope()) { foreach (var childNode in node.Nodes) { childNode.Accept(this, context); context.AppendLine(); } } context.AppendLine("end"); return true; } public bool VisitVariableArgumentsExpressionNode(VariableArgumentsExpressionNode node, Context context) { context.Append("..."); return true; } void VisitSyntaxNodes(SyntaxNode[] nodes, Context context) { for (int i = 0; i < nodes.Length; i++) { nodes[i].Accept(this, context); if (i != nodes.Length - 1) context.Append(", "); } } public bool VisitGroupedExpressionNode(GroupedExpressionNode node, Context context) { context.Append("("); node.Expression.Accept(this, context); context.Append(")"); return true; } }