|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
namespace CSharpSyntaxGenerator
{
internal class SourceWriter : AbstractFileWriter
{
private SourceWriter(TextWriter writer, Tree tree, CancellationToken cancellationToken = default)
: base(writer, tree, cancellationToken)
{
}
public static void WriteMain(TextWriter writer, Tree tree, CancellationToken cancellationToken = default) => new SourceWriter(writer, tree, cancellationToken).WriteMain();
public static void WriteInternal(TextWriter writer, Tree tree, CancellationToken cancellationToken = default) => new SourceWriter(writer, tree, cancellationToken).WriteInternal();
public static void WriteSyntax(TextWriter writer, Tree tree, CancellationToken cancellationToken = default) => new SourceWriter(writer, tree, cancellationToken).WriteSyntax();
private void WriteFileHeader()
{
WriteLine("// <auto-generated />");
WriteLine();
WriteLine("#nullable enable");
WriteLine();
WriteLine("using System;");
WriteLine("using System.Collections.Generic;");
WriteLine("using System.Diagnostics.CodeAnalysis;");
WriteLine("using Microsoft.CodeAnalysis.Syntax.InternalSyntax;");
WriteLine("using Roslyn.Utilities;");
WriteLine("using CoreSyntax = Microsoft.CodeAnalysis.Syntax.InternalSyntax;");
WriteLine();
}
private void WriteInternal()
{
WriteFileHeader();
WriteLine("namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax;");
this.WriteGreenTypes();
this.WriteGreenVisitors();
this.WriteGreenRewriter();
this.WriteContextualGreenFactories();
this.WriteStaticGreenFactories();
}
private void WriteSyntax()
{
WriteFileHeader();
WriteLine("namespace Microsoft.CodeAnalysis.CSharp.Syntax;");
this.WriteRedTypes();
}
private void WriteMain()
{
WriteFileHeader();
WriteLine("namespace Microsoft.CodeAnalysis.CSharp;");
WriteLine("using System.Diagnostics.CodeAnalysis;");
WriteLine("using Microsoft.CodeAnalysis.CSharp.Syntax;");
this.WriteRedVisitors();
this.WriteRedRewriter();
this.WriteRedFactories();
}
private void WriteGreenTypes()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList();
foreach (var node in nodes)
{
WriteLine();
this.WriteGreenType(node);
}
}
private void WriteGreenType(TreeType node)
{
WriteComment(node.TypeComment, "");
if (node is AbstractNode)
{
var nd = (AbstractNode)node;
WriteLine($"internal abstract partial class {node.Name} : {node.Base}");
OpenBlock();
// ctor with diagnostics and annotations
WriteLine($"internal {node.Name}(SyntaxKind kind, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations)");
WriteLine(" : base(kind, diagnostics, annotations)");
OpenBlock();
if (node.Name == "DirectiveTriviaSyntax")
{
WriteLine("SetFlags(NodeFlags.ContainsDirectives);");
}
CloseBlock();
WriteLine();
// ctor without diagnostics and annotations
WriteLine($"internal {node.Name}(SyntaxKind kind)");
WriteLine(" : base(kind)");
OpenBlock();
if (node.Name == "DirectiveTriviaSyntax")
{
WriteLine("SetFlags(NodeFlags.ContainsDirectives);");
}
CloseBlock();
var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();
foreach (var field in nodeFields)
{
if (IsNodeOrNodeList(field.Type))
{
WriteLine();
WriteComment(field.PropertyComment, "");
if (IsSeparatedNodeList(field.Type) ||
IsNodeList(field.Type))
{
WriteLine($"public abstract {(IsNew(field) ? "new " : "")}CoreSyntax.{field.Type} {field.Name} {{ get; }}");
}
else
{
WriteLine($"public abstract {(IsNew(field) ? "new " : "")}{(GetFieldType(field, green: true))} {field.Name} {{ get; }}");
}
}
}
foreach (var field in valueFields)
{
WriteLine();
WriteComment(field.PropertyComment, "");
WriteLine($"public abstract {(IsNew(field) ? "new " : "")}{field.Type} {field.Name} {{ get; }}");
}
CloseBlock();
}
else if (node is Node)
{
var nd = (Node)node;
WriteLine($"internal sealed partial class {node.Name} : {node.Base}");
OpenBlock();
var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();
foreach (var field in nodeFields)
{
var type = GetFieldType(field, green: true);
WriteLine($"internal readonly {type} {CamelCase(field.Name)};");
}
foreach (var field in valueFields)
{
WriteLine($"internal readonly {field.Type} {CamelCase(field.Name)};");
}
// write constructor with diagnostics and annotations
WriteLine();
Write($"internal {node.Name}(SyntaxKind kind");
WriteGreenNodeConstructorArgs(nodeFields, valueFields);
WriteLine(", DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations)");
WriteLine(" : base(kind, diagnostics, annotations)");
OpenBlock();
WriteCtorBody(nd, valueFields, nodeFields);
CloseBlock();
// write constructor with async
WriteLine();
Write($"internal {node.Name}(SyntaxKind kind");
WriteGreenNodeConstructorArgs(nodeFields, valueFields);
WriteLine(", SyntaxFactoryContext context)");
WriteLine(" : base(kind)");
OpenBlock();
WriteLine("this.SetFactoryContext(context);");
WriteCtorBody(nd, valueFields, nodeFields);
CloseBlock();
// write constructor without diagnostics and annotations
WriteLine();
Write($"internal {node.Name}(SyntaxKind kind");
WriteGreenNodeConstructorArgs(nodeFields, valueFields);
WriteLine(")");
WriteLine(" : base(kind)");
OpenBlock();
WriteCtorBody(nd, valueFields, nodeFields);
CloseBlock();
WriteLine();
// property accessors
foreach (var field in nodeFields)
{
WriteComment(field.PropertyComment, "");
if (IsNodeList(field.Type))
{
var type = $"CoreSyntax.{field.Type}";
WriteLine($"public {OverrideOrNewModifier(field)}{type} {field.Name} => new {type}(this.{CamelCase(field.Name)});");
}
else if (IsSeparatedNodeList(field.Type))
{
var type = $"CoreSyntax.{field.Type}";
WriteLine($"public {OverrideOrNewModifier(field)}{type} {field.Name} => new {type}(new CoreSyntax.SyntaxList<CSharpSyntaxNode>(this.{CamelCase(field.Name)}));");
}
else if (field.Type == "SyntaxNodeOrTokenList")
{
var type = $"CoreSyntax.SyntaxList<CSharpSyntaxNode>";
WriteLine($"public {OverrideOrNewModifier(field)}{type} {field.Name} => new {type}(this.{CamelCase(field.Name)});");
}
else
{
WriteLine($"public {OverrideOrNewModifier(field)}{(GetFieldType(field, green: true))} {field.Name} => this.{CamelCase(field.Name)};");
}
}
foreach (var field in valueFields)
{
WriteComment(field.PropertyComment, "");
WriteLine($"public {OverrideOrNewModifier(field)}{field.Type} {field.Name} => this.{CamelCase(field.Name)};");
}
// GetSlot
WriteLine();
Write("internal override GreenNode? GetSlot(int index)");
if (nodeFields.Count == 0)
{
WriteLine(" => null;");
}
else if (nodeFields.Count == 1)
{
WriteLine();
Indent();
WriteLine($"=> index == 0 ? this.{CamelCase(nodeFields[0].Name)} : null;");
Unindent();
}
else
{
WriteLine();
Indent();
WriteLine("=> index switch");
OpenBlock();
for (int i = 0, n = nodeFields.Count; i < n; i++)
{
var field = nodeFields[i];
WriteLine($"{i} => this.{CamelCase(field.Name)},");
}
WriteLine("_ => null,");
CloseBlock(";");
Unindent();
}
WriteLine();
WriteLine($"internal override SyntaxNode CreateRed(SyntaxNode? parent, int position) => new CSharp.Syntax.{node.Name}(this, parent, position);");
this.WriteGreenAcceptMethods(nd);
this.WriteGreenUpdateMethod(nd);
this.WriteSetDiagnostics(nd);
this.WriteSetAnnotations(nd);
CloseBlock();
}
}
private void WriteGreenNodeConstructorArgs(List<Field> nodeFields, List<Field> valueFields)
{
foreach (var field in nodeFields)
{
Write($", {(GetFieldType(field, green: true))} {CamelCase(field.Name)}");
}
foreach (var field in valueFields)
{
Write($", {field.Type} {CamelCase(field.Name)}");
}
}
private void WriteCtorBody(Node node, List<Field> valueFields, List<Field> nodeFields)
{
if (node.Name == "AttributeSyntax")
{
WriteLine("SetFlags(NodeFlags.ContainsAttributes);");
}
// constructor body
WriteLine($"this.SlotCount = {nodeFields.Count};");
foreach (var field in nodeFields)
{
if (IsAnyList(field.Type) || IsOptional(field))
{
WriteLine($"if ({CamelCase(field.Name)} != null)");
OpenBlock();
WriteLine($"this.AdjustFlagsAndWidth({CamelCase(field.Name)});");
WriteLine($"this.{CamelCase(field.Name)} = {CamelCase(field.Name)};");
CloseBlock();
}
else
{
WriteLine($"this.AdjustFlagsAndWidth({CamelCase(field.Name)});");
WriteLine($"this.{CamelCase(field.Name)} = {CamelCase(field.Name)};");
}
}
foreach (var field in valueFields)
{
WriteLine($"this.{CamelCase(field.Name)} = {CamelCase(field.Name)};");
}
}
private void WriteSetAnnotations(Node node)
{
WriteLine();
WriteLine("internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations)");
Write($" => new {node.Name}(");
Write(CommaJoin(
"this.Kind",
node.Fields.Select(f => $"this.{CamelCase(f.Name)}"),
"GetDiagnostics()",
"annotations"));
WriteLine(");");
}
private void WriteSetDiagnostics(Node node)
{
WriteLine();
WriteLine("internal override GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics)");
Write($" => new {node.Name}(");
Write(CommaJoin(
"this.Kind",
node.Fields.Select(f => $"this.{CamelCase(f.Name)}"),
"diagnostics",
"GetAnnotations()"));
WriteLine(");");
}
private void WriteGreenAcceptMethods(Node node)
{
WriteLine();
WriteLine($"public override void Accept(CSharpSyntaxVisitor visitor) => visitor.Visit{StripPost(node.Name, "Syntax")}(this);");
WriteLine($"public override TResult Accept<TResult>(CSharpSyntaxVisitor<TResult> visitor) => visitor.Visit{StripPost(node.Name, "Syntax")}(this);");
}
private void WriteGreenVisitors()
{
WriteGreenVisitor(withResult: true);
WriteGreenVisitor(withResult: false);
}
private void WriteGreenVisitor(bool withResult)
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList();
WriteLine();
WriteLine("internal partial class CSharpSyntaxVisitor" + (withResult ? "<TResult>" : ""));
OpenBlock();
foreach (var node in nodes.OfType<Node>())
{
WriteLine($"public virtual {(withResult ? "TResult" : "void")} Visit{StripPost(node.Name, "Syntax")}({node.Name} node) => this.DefaultVisit(node);");
}
CloseBlock();
}
private void WriteGreenUpdateMethod(Node node)
{
WriteLine();
Write($"public {node.Name} Update(");
Write(CommaJoin(node.Fields.Select(f =>
{
var type =
f.Type == "SyntaxNodeOrTokenList" ? "CoreSyntax.SyntaxList<CSharpSyntaxNode>" :
f.Type == "SyntaxTokenList" ? "CoreSyntax.SyntaxList<SyntaxToken>" :
IsNodeList(f.Type) ? "CoreSyntax." + f.Type :
IsSeparatedNodeList(f.Type) ? "CoreSyntax." + f.Type :
f.Type;
return $"{type} {CamelCase(f.Name)}";
})));
WriteLine(")");
OpenBlock();
Write("if (");
int nCompared = 0;
foreach (var field in node.Fields)
{
if (IsDerivedOrListOfDerived("SyntaxNode", field.Type) || IsDerivedOrListOfDerived("SyntaxToken", field.Type) || field.Type == "SyntaxNodeOrTokenList")
{
if (nCompared > 0)
Write(" || ");
Write($"{CamelCase(field.Name)} != this.{field.Name}");
nCompared++;
}
}
if (nCompared > 0)
{
WriteLine(")");
OpenBlock();
Write($"var newNode = SyntaxFactory.{StripPost(node.Name, "Syntax")}(");
Write(CommaJoin(
node.Kinds.Count > 1 ? "this.Kind" : "",
node.Fields.Select(f => CamelCase(f.Name))));
WriteLine(");");
WriteLine("var diags = GetDiagnostics();");
WriteLine("if (diags?.Length > 0)");
WriteLine(" newNode = newNode.WithDiagnosticsGreen(diags);");
WriteLine("var annotations = GetAnnotations();");
WriteLine("if (annotations?.Length > 0)");
WriteLine(" newNode = newNode.WithAnnotationsGreen(annotations);");
WriteLine("return newNode;");
CloseBlock();
}
WriteLine();
WriteLine("return this;");
CloseBlock();
}
private void WriteGreenRewriter()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList();
WriteLine();
WriteLine("internal partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor<CSharpSyntaxNode>");
OpenBlock();
int nWritten = 0;
foreach (var node in nodes.OfType<Node>())
{
var nodeFields = node.Fields.Where(nd => IsNodeOrNodeList(nd.Type)).ToList();
if (nWritten > 0)
WriteLine();
nWritten++;
WriteLine($"public override CSharpSyntaxNode Visit{StripPost(node.Name, "Syntax")}({node.Name} node)");
Indent();
if (nodeFields.Count == 0)
{
WriteLine("=> node;");
}
else
{
Write("=> node.Update(");
Write(CommaJoin(node.Fields.Select(f =>
{
if (IsAnyList(f.Type))
return $"VisitList(node.{f.Name})";
else if (IsNode(f.Type))
return $"({f.Type})Visit(node.{f.Name})";
else
return $"node.{f.Name}";
})));
WriteLine(");");
}
Unindent();
}
CloseBlock();
}
private void WriteContextualGreenFactories()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode and not AbstractNode).ToList();
WriteLine();
WriteLine("internal partial class ContextAwareSyntax");
OpenBlock();
WriteLine();
WriteLine("private SyntaxFactoryContext context;");
WriteLine();
WriteLine("public ContextAwareSyntax(SyntaxFactoryContext context)");
WriteLine(" => this.context = context;");
WriteGreenFactories(nodes, withSyntaxFactoryContext: true);
CloseBlock();
}
private void WriteStaticGreenFactories()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode and not AbstractNode).ToList();
WriteLine();
WriteLine("internal static partial class SyntaxFactory");
OpenBlock();
WriteGreenFactories(nodes);
CloseBlock();
}
private void WriteGreenFactories(List<TreeType> nodes, bool withSyntaxFactoryContext = false)
{
foreach (var node in nodes.OfType<Node>())
{
WriteLine();
this.WriteGreenFactory(node, withSyntaxFactoryContext);
}
}
private void WriteGreenFactory(Node nd, bool withSyntaxFactoryContext = false)
{
var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();
Write($"public {(withSyntaxFactoryContext ? "" : "static ")}{nd.Name} {StripPost(nd.Name, "Syntax")}(");
WriteGreenFactoryParameters(nd);
WriteLine(")");
OpenBlock();
// validate kind
if (nd.Kinds.Count >= 2)
{
WriteLine("switch (kind)");
OpenBlock();
var kinds = nd.Kinds.Distinct().ToList();
foreach (var kind in kinds)
{
WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds.Last() ? " break;" : "")}");
}
WriteLine("default: throw new ArgumentException(nameof(kind));");
CloseBlock();
}
// validate parameters
WriteLineWithoutIndent("#if DEBUG");
foreach (var field in nodeFields)
{
var pname = CamelCase(field.Name);
if (!IsAnyList(field.Type) && !IsOptional(field))
{
WriteLine($"if ({CamelCase(field.Name)} == null) throw new ArgumentNullException(nameof({CamelCase(field.Name)}));");
}
if (field.Type == "SyntaxToken" && field.Kinds != null && field.Kinds.Count > 0)
{
if (IsOptional(field))
{
WriteLine($"if ({CamelCase(field.Name)} != null)");
OpenBlock();
}
if (field.Kinds.Count == 1 && !IsOptional(field))
{
WriteLine($"if ({pname}.Kind != SyntaxKind.{field.Kinds[0].Name}) throw new ArgumentException(nameof({pname}));");
}
else
{
WriteLine($"switch ({pname}.Kind)");
OpenBlock();
var kinds = field.Kinds.Distinct().ToList();
//we need to check for Kind=None as well as node == null because that's what the red factory will pass
if (IsOptional(field))
{
kinds.Add(new Kind { Name = "None" });
}
foreach (var kind in kinds)
{
WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds.Last() ? " break;" : "")}");
}
WriteLine($"default: throw new ArgumentException(nameof({pname}));");
CloseBlock();
}
if (IsOptional(field))
{
CloseBlock();
}
}
}
WriteLineWithoutIndent("#endif");
if (nd.Name != "SkippedTokensTriviaSyntax" &&
nd.Name != "DocumentationCommentTriviaSyntax" &&
nd.Name != "IncompleteMemberSyntax" &&
nd.Name != "AttributeSyntax" &&
valueFields.Count + nodeFields.Count <= 3)
{
//int hash;
//var cached = SyntaxNodeCache.TryGetNode((int)SyntaxKind.IdentifierName, identifier, this.context, out hash);
//if (cached != null) return (IdentifierNameSyntax)cached;
//var result = new IdentifierNameSyntax(SyntaxKind.IdentifierName, identifier, this.context);
//if (hash >= 0)
//{
// SyntaxNodeCache.AddNode(result, hash);
//}
//return result;
WriteLine();
//int hash;
WriteLine("int hash;");
//SyntaxNode cached = SyntaxNodeCache.TryGetNode(SyntaxKind.IdentifierName, identifier, this.context, out hash);
if (withSyntaxFactoryContext)
{
Write("var cached = CSharpSyntaxNodeCache.TryGetNode((int)");
}
else
{
Write("var cached = SyntaxNodeCache.TryGetNode((int)");
}
WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields);
WriteLine(", out hash);");
// if (cached != null) return (IdentifierNameSyntax)cached;
WriteLine($"if (cached != null) return ({nd.Name})cached;");
WriteLine();
//var result = new IdentifierNameSyntax(SyntaxKind.IdentifierName, identifier);
Write($"var result = new {nd.Name}(");
WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields);
WriteLine(");");
//if (hash >= 0)
WriteLine("if (hash >= 0)");
//{
OpenBlock();
// SyntaxNodeCache.AddNode(result, hash);
WriteLine("SyntaxNodeCache.AddNode(result, hash);");
//}
CloseBlock();
WriteLine();
//return result;
WriteLine("return result;");
}
else
{
WriteLine();
Write($"return new {nd.Name}(");
WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields);
WriteLine(");");
}
CloseBlock();
}
private void WriteGreenFactoryParameters(Node nd)
{
Write(CommaJoin(
nd.Kinds.Count > 1 ? "SyntaxKind kind" : "",
nd.Fields.Select(f =>
{
var type = f.Type switch
{
"SyntaxNodeOrTokenList" => "CoreSyntax.SyntaxList<CSharpSyntaxNode>",
_ when IsSeparatedNodeList(f.Type) || IsNodeList(f.Type) => $"CoreSyntax.{f.Type}",
_ => GetFieldType(f, green: true),
};
return $"{type} {CamelCase(f.Name)}";
})));
}
private void WriteCtorArgList(Node nd, bool withSyntaxFactoryContext, List<Field> valueFields, List<Field> nodeFields)
{
Write(CommaJoin(
nd.Kinds.Count == 1 ? $"SyntaxKind.{nd.Kinds[0].Name}" : "kind",
nodeFields.Select(f =>
f.Type == "SyntaxList<SyntaxToken>" || IsAnyList(f.Type)
? $"{CamelCase(f.Name)}.Node"
: CamelCase(f.Name)),
// values are at end
valueFields.Select(f => CamelCase(f.Name)),
withSyntaxFactoryContext ? "this.context" : ""));
}
private void WriteRedTypes()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList();
foreach (var node in nodes)
{
WriteLine();
this.WriteRedType(node);
}
}
private List<Field> GetNodeOrNodeListFields(TreeType node)
=> node is AbstractNode an
? an.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList()
: node is Node nd
? nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList()
: new List<Field>();
private void WriteRedType(TreeType node)
{
WriteComment(node.TypeComment, "");
if (node is AbstractNode)
{
var nd = (AbstractNode)node;
WriteLine($"public abstract partial class {node.Name} : {node.Base}");
OpenBlock();
WriteLine($"internal {node.Name}(InternalSyntax.CSharpSyntaxNode green, SyntaxNode? parent, int position)");
WriteLine(" : base(green, parent, position)");
OpenBlock();
CloseBlock();
var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = GetNodeOrNodeListFields(nd);
foreach (var field in nodeFields)
{
if (IsNodeOrNodeList(field.Type))
{
//red SyntaxLists can't contain tokens, so we switch to SyntaxTokenList
var fieldType = GetRedFieldType(field);
WriteLine();
WriteComment(field.PropertyComment, "");
WriteLine($"{"public"} abstract {(IsNew(field) ? "new " : "")}{fieldType} {field.Name} {{ get; }}");
WriteLine($"public {node.Name} With{field.Name}({fieldType} {CamelCase(field.Name)}) => With{field.Name}Core({CamelCase(field.Name)});");
WriteLine($"internal abstract {node.Name} With{field.Name}Core({fieldType} {CamelCase(field.Name)});");
if (IsAnyList(field.Type))
{
var argType = GetElementType(field.Type);
WriteLine();
WriteLine($"public {node.Name} Add{field.Name}(params {argType}[] items) => Add{field.Name}Core(items);");
WriteLine($"internal abstract {node.Name} Add{field.Name}Core(params {argType}[] items);");
}
else
{
var referencedNode = TryGetNodeForNestedList(field);
if (referencedNode != null)
{
foreach (var referencedNodeField in referencedNode.Fields)
{
if (IsAnyList(referencedNodeField.Type))
{
var argType = GetElementType(referencedNodeField.Type);
WriteLine();
WriteLine($"public {node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items) => Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}Core(items);");
WriteLine($"internal abstract {node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}Core(params {argType}[] items);");
}
}
}
}
}
}
foreach (var field in valueFields)
{
WriteLine();
WriteComment(field.PropertyComment, "");
WriteLine($"{"public"} abstract {(IsNew(field) ? "new " : "")}{field.Type} {field.Name} {{ get; }}");
}
var baseType = GetTreeType(node.Base);
if (baseType != null)
{
var baseNodeFields = GetNodeOrNodeListFields(baseType);
if (baseNodeFields.Count > 0)
{
WriteLine();
}
foreach (var baseField in baseNodeFields)
{
WriteLine($"public new {node.Name} With{baseField.Name}({GetRedFieldType(baseField)} {CamelCase(baseField.Name)}) => ({node.Name})With{baseField.Name}Core({CamelCase(baseField.Name)});");
}
foreach (var baseField in baseNodeFields)
{
if (IsAnyList(baseField.Type))
{
var argType = GetElementType(baseField.Type);
WriteLine();
WriteLine($"public new {node.Name} Add{baseField.Name}(params {argType}[] items) => ({node.Name})Add{baseField.Name}Core(items);");
}
else
{
var referencedNode = TryGetNodeForNestedList(baseField);
if (referencedNode != null)
{
// look for list members...
foreach (var referencedNodeField in referencedNode.Fields)
{
if (IsAnyList(referencedNodeField.Type))
{
var argType = GetElementType(referencedNodeField.Type);
WriteLine();
WriteLine($"public new {baseType.Name} Add{StripPost(baseField.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items) => Add{StripPost(baseField.Name, "Opt")}{referencedNodeField.Name}Core(items);");
}
}
}
}
}
}
CloseBlock();
}
else if (node is Node)
{
var nd = (Node)node;
WriteComment($"<remarks>");
WriteComment($"<para>This node is associated with the following syntax kinds:</para>");
WriteComment($"<list type=\"bullet\">");
foreach (var kind in nd.Kinds)
{
WriteComment($"<item><description><see cref=\"SyntaxKind.{kind.Name}\"/></description></item>");
}
WriteComment($"</list>");
WriteComment($"</remarks>");
WriteLine($"public sealed partial class {node.Name} : {node.Base}");
OpenBlock();
var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();
foreach (var field in nodeFields)
{
if (field.Type is not "SyntaxToken"
and not "SyntaxList<SyntaxToken>")
{
if (IsSeparatedNodeList(field.Type) || field.Type == "SyntaxNodeOrTokenList")
{
WriteLine($"private SyntaxNode? {CamelCase(field.Name)};");
}
else
{
var type = GetFieldType(field, green: false);
WriteLine($"private {type} {CamelCase(field.Name)};");
}
}
}
// write constructor
WriteLine();
WriteLine($"internal {node.Name}(InternalSyntax.CSharpSyntaxNode green, SyntaxNode? parent, int position)");
WriteLine(" : base(green, parent, position)");
OpenBlock();
CloseBlock();
WriteLine();
// property accessors
for (int i = 0, n = nodeFields.Count; i < n; i++)
{
var field = nodeFields[i];
if (field.Type == "SyntaxToken")
{
WriteComment(field.PropertyComment, "");
Write($"public {OverrideOrNewModifier(field)}{GetRedPropertyType(field)} {field.Name}");
if (IsOptional(field))
{
WriteLine();
OpenBlock();
WriteLine("get");
OpenBlock();
WriteLine($"var slot = ((Syntax.InternalSyntax.{node.Name})this.Green).{CamelCase(field.Name)};");
WriteLine($"return slot != null ? new SyntaxToken(this, slot, {GetChildPosition(i)}, {GetChildIndex(i)}) : default;");
CloseBlock();
CloseBlock();
}
else
{
WriteLine($" => new SyntaxToken(this, ((InternalSyntax.{node.Name})this.Green).{CamelCase(field.Name)}, {GetChildPosition(i)}, {GetChildIndex(i)});");
}
}
else if (field.Type == "SyntaxList<SyntaxToken>")
{
WriteComment(field.PropertyComment, "");
WriteLine($"public {OverrideOrNewModifier(field)}SyntaxTokenList {field.Name}");
OpenBlock();
WriteLine("get");
OpenBlock();
WriteLine($"var slot = this.Green.GetSlot({i});");
WriteLine($"return slot != null ? new SyntaxTokenList(this, slot, {GetChildPosition(i)}, {GetChildIndex(i)}) : default;");
CloseBlock();
CloseBlock();
}
else
{
WriteComment(field.PropertyComment, "");
Write($"public {OverrideOrNewModifier(field)}{GetRedPropertyType(field)} {field.Name}");
if (IsNodeList(field.Type))
{
WriteLine($" => new {field.Type}(GetRed(ref this.{CamelCase(field.Name)}, {i}));");
}
else if (IsSeparatedNodeList(field.Type))
{
WriteLine();
OpenBlock();
WriteLine("get");
OpenBlock();
WriteLine($"var red = GetRed(ref this.{CamelCase(field.Name)}, {i});");
WriteLine($"return red != null ? new {field.Type}(red, {GetChildIndex(i)}) : default;");
CloseBlock();
CloseBlock();
}
else if (field.Type == "SyntaxNodeOrTokenList")
{
throw new InvalidOperationException("field cannot be a random SyntaxNodeOrTokenList");
}
else
{
var suffix = IsOptional(field) ? "" : "!";
if (i == 0)
{
WriteLine($" => GetRedAtZero(ref this.{CamelCase(field.Name)}){suffix};");
}
else
{
WriteLine($" => GetRed(ref this.{CamelCase(field.Name)}, {i}){suffix};");
}
}
}
WriteLine();
}
foreach (var field in valueFields)
{
WriteComment(field.PropertyComment, "");
WriteLine($"{"public"} {OverrideOrNewModifier(field)}{field.Type} {field.Name} => ((InternalSyntax.{node.Name})this.Green).{field.Name};");
WriteLine();
}
{
//GetNodeSlot forces creation of a red node.
Write("internal override SyntaxNode? GetNodeSlot(int index)");
var relevantNodes = nodeFields.Select((field, index) => (field, index))
.Where(t => t.field.Type is not "SyntaxToken" and not "SyntaxList<SyntaxToken>");
if (!relevantNodes.Any())
{
WriteLine(" => null;");
}
else if (relevantNodes.Count() == 1)
{
var (field, index) = relevantNodes.Single();
var whenTrue = index == 0
? $"GetRedAtZero(ref this.{CamelCase(field.Name)})"
: $"GetRed(ref this.{CamelCase(field.Name)}, {index})";
var suffix = IsOptional(field) ? "" : "!";
WriteLine($" => index == {index} ? {whenTrue}{suffix} : null;");
}
else
{
WriteLine();
Indent();
WriteLine("=> index switch");
OpenBlock();
foreach (var (field, index) in relevantNodes)
{
var suffix = IsOptional(field) ? "" : "!";
if (index == 0)
{
WriteLine($"{index} => GetRedAtZero(ref this.{CamelCase(field.Name)}){suffix},");
}
else
{
WriteLine($"{index} => GetRed(ref this.{CamelCase(field.Name)}, {index}){suffix},");
}
}
WriteLine("_ => null,");
CloseBlock(";");
Unindent();
}
}
WriteLine();
{
//GetCachedSlot returns a red node if we have it.
Write("internal override SyntaxNode? GetCachedSlot(int index)");
var relevantNodes = nodeFields.Select((field, index) => (field, index))
.Where(t => t.field.Type is not "SyntaxToken" and not "SyntaxList<SyntaxToken>");
if (!relevantNodes.Any())
{
WriteLine(" => null;");
}
else if (relevantNodes.Count() == 1)
{
var (field, index) = relevantNodes.Single();
WriteLine($" => index == {index} ? this.{CamelCase(field.Name)} : null;");
}
else
{
WriteLine();
Indent();
WriteLine("=> index switch");
OpenBlock();
foreach (var (field, index) in relevantNodes)
{
WriteLine($"{index} => this.{CamelCase(field.Name)},");
}
WriteLine("_ => null,");
CloseBlock(";");
Unindent();
}
}
this.WriteRedAcceptMethods(nd);
this.WriteRedUpdateMethod(nd);
this.WriteRedWithMethods(nd);
this.WriteRedListHelperMethods(nd);
CloseBlock();
}
}
private string GetRedFieldType(Field field)
{
if (field.Type == "SyntaxList<SyntaxToken>")
return "SyntaxTokenList";
if (IsOptional(field) && IsNode(field.Type) && field.Type != "SyntaxToken")
return field.Type + "?";
return field.Type;
}
private string GetChildPosition(int i)
=> i == 0 ? "Position" : "GetChildPosition(" + i + ")";
private string GetChildIndex(int i)
=> i == 0 ? "0" : "GetChildIndex(" + i + ")";
private void WriteRedAcceptMethods(Node node)
{
WriteLine();
WriteRedAcceptMethod(node, false);
WriteRedAcceptMethod(node, true);
}
private void WriteRedAcceptMethod(Node node, bool genericResult)
{
string genericArgs = genericResult ? "<TResult>" : "";
WriteLine($"public override {(genericResult ? "TResult?" : "void")} Accept{genericArgs}(CSharpSyntaxVisitor{genericArgs} visitor){(genericResult ? " where TResult : default" : "")} => visitor.Visit{StripPost(node.Name, "Syntax")}(this);");
}
private void WriteRedVisitors()
{
WriteRedVisitor(genericResult: true);
WriteRedVisitor(genericResult: false);
}
private void WriteRedVisitor(bool genericResult)
{
string genericArgs = genericResult ? "<TResult>" : "";
var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList();
WriteLine();
WriteLine("public partial class CSharpSyntaxVisitor" + genericArgs);
OpenBlock();
int nWritten = 0;
foreach (var node in nodes.OfType<Node>())
{
if (nWritten > 0)
WriteLine();
nWritten++;
WriteComment($"<summary>Called when the visitor visits a {node.Name} node.</summary>");
WriteLine($"public virtual {(genericResult ? "TResult?" : "void")} Visit{StripPost(node.Name, "Syntax")}({node.Name} node) => this.DefaultVisit(node);");
}
CloseBlock();
}
private void WriteRedUpdateMethod(Node node)
{
WriteLine();
Write($"public {node.Name} Update(");
Write(CommaJoin(
node.Fields.Select(f => $"{GetRedPropertyType(f)} {CamelCase(f.Name)}")));
WriteLine(")");
OpenBlock();
Write("if (");
int nCompared = 0;
foreach (var field in node.Fields)
{
if (IsDerivedOrListOfDerived("SyntaxNode", field.Type) || IsDerivedOrListOfDerived("SyntaxToken", field.Type) || field.Type == "SyntaxNodeOrTokenList")
{
if (nCompared > 0)
Write(" || ");
Write($"{CamelCase(field.Name)} != this.{field.Name}");
nCompared++;
}
}
if (nCompared > 0)
{
WriteLine(")");
OpenBlock();
Write($"var newNode = SyntaxFactory.{StripPost(node.Name, "Syntax")}(");
Write(CommaJoin(
node.Kinds.Count > 1 ? "this.Kind()" : "",
node.Fields.Select(f => CamelCase(f.Name))));
WriteLine(");");
WriteLine("var annotations = GetAnnotations();");
WriteLine("return annotations?.Length > 0 ? newNode.WithAnnotations(annotations) : newNode;");
CloseBlock();
}
WriteLine();
WriteLine("return this;");
CloseBlock();
}
private void WriteRedWithMethods(Node node)
{
foreach (var field in node.Fields)
{
var type = this.GetRedPropertyType(field);
if (field == node.Fields.First())
{
WriteLine();
}
var isNew = false;
if (IsOverride(field))
{
var (baseType, baseField) = GetHighestBaseTypeWithField(node, field.Name);
if (baseType != null)
{
Write($"internal override {baseType.Name} With{field.Name}Core({GetRedPropertyType(baseField)} {CamelCase(field.Name)}) => With{field.Name}({CamelCase(field.Name)}");
if (baseField.Type != "SyntaxToken" && IsOptional(baseField) && !IsOptional(field))
{
Write($" ?? throw new ArgumentNullException(nameof({CamelCase(field.Name)}))");
}
WriteLine(");");
isNew = true;
}
}
Write(
$"public{(isNew ? " new " : " ")}{node.Name} With{StripPost(field.Name, "Opt")}({type} {CamelCase(field.Name)})" +
" => Update(");
// call update inside each setter
Write(CommaJoin(node.Fields.Select(f =>
f == field ? CamelCase(f.Name) : $"this.{f.Name}")));
WriteLine(");");
}
}
private (TreeType type, Field field) GetHighestBaseTypeWithField(TreeType node, string name)
{
TreeType bestType = null;
Field bestField = null;
for (var current = node; current != null; current = TryGetBaseType(current))
{
var fields = GetNodeOrNodeListFields(current);
var field = fields.FirstOrDefault(f => f.Name == name);
if (field != null)
{
bestType = current;
bestField = field;
}
}
return (bestType, bestField);
}
private TreeType TryGetBaseType(TreeType node)
=> node is AbstractNode an
? GetTreeType(an.Base)
: node is Node n
? GetTreeType(n.Base)
: null;
private void WriteRedListHelperMethods(Node node)
{
var wroteNewLine = false;
foreach (var field in node.Fields)
{
if (IsAnyList(field.Type))
{
if (!wroteNewLine)
{
WriteLine();
wroteNewLine = true;
}
// write list helper methods for list properties
WriteRedListHelperMethods(node, field);
}
else
{
var referencedNode = TryGetNodeForNestedList(field);
if (referencedNode != null)
{
// look for list members...
foreach (var referencedNodeField in referencedNode.Fields)
{
if (IsAnyList(referencedNodeField.Type))
{
if (!wroteNewLine)
{
WriteLine();
wroteNewLine = true;
}
WriteRedNestedListHelperMethods(node, field, referencedNode, referencedNodeField);
}
}
}
}
}
}
private Node TryGetNodeForNestedList(Field field)
{
Node referencedNode = GetNode(field.Type);
if (referencedNode != null && (!IsOptional(field) || RequiredFactoryArgumentCount(referencedNode) == 0))
{
return referencedNode;
}
return null;
}
private void WriteRedListHelperMethods(Node node, Field field)
{
var argType = GetElementType(field.Type);
var isNew = false;
if (IsOverride(field))
{
var (baseType, baseField) = GetHighestBaseTypeWithField(node, field.Name);
if (baseType != null)
{
var baseArgType = GetElementType(baseField.Type);
WriteLine($"internal override {baseType.Name} Add{field.Name}Core(params {baseArgType}[] items) => Add{field.Name}(items);");
isNew = true;
}
}
WriteLine($"public{(isNew ? " new " : " ")}{node.Name} Add{field.Name}(params {argType}[] items) => With{StripPost(field.Name, "Opt")}(this.{field.Name}.AddRange(items));");
}
private void WriteRedNestedListHelperMethods(Node node, Field field, Node referencedNode, Field referencedNodeField)
{
var argType = GetElementType(referencedNodeField.Type);
var isNew = false;
if (IsOverride(field))
{
var (baseType, _) = GetHighestBaseTypeWithField(node, field.Name);
if (baseType != null)
{
WriteLine($"internal override {baseType.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}Core(params {argType}[] items) => Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(items);");
isNew = true;
}
}
// AddBaseListTypes
Write($"public{(isNew ? " new " : " ")}{node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items)");
if (IsOptional(field))
{
WriteLine();
OpenBlock();
var factoryName = StripPost(referencedNode.Name, "Syntax");
var varName = StripPost(CamelCase(field.Name), "Opt");
WriteLine($"var {varName} = this.{field.Name} ?? SyntaxFactory.{factoryName}();");
WriteLine($"return With{StripPost(field.Name, "Opt")}({varName}.With{StripPost(referencedNodeField.Name, "Opt")}({varName}.{referencedNodeField.Name}.AddRange(items)));");
CloseBlock();
}
else
{
WriteLine($" => With{StripPost(field.Name, "Opt")}(this.{field.Name}.With{StripPost(referencedNodeField.Name, "Opt")}(this.{field.Name}.{referencedNodeField.Name}.AddRange(items)));");
}
}
private void WriteRedRewriter()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList();
WriteLine();
WriteLine("public partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor<SyntaxNode?>");
OpenBlock();
int nWritten = 0;
foreach (var node in nodes.OfType<Node>())
{
if (nWritten > 0)
WriteLine();
nWritten++;
WriteLine($"public override SyntaxNode? Visit{StripPost(node.Name, "Syntax")}({node.Name} node)");
if (node.Fields.Count == 0)
{
WriteLine(" => node;");
}
else
{
Write(" => node.Update(");
Write(CommaJoin(node.Fields.Select(f =>
{
if (IsNodeOrNodeList(f.Type))
{
if (IsAnyList(f.Type))
return $"VisitList(node.{f.Name})";
else if (f.Type == "SyntaxToken")
return $"VisitToken(node.{f.Name})";
else if (IsOptional(f))
return $"({(GetFieldType(f, green: false))})Visit(node.{f.Name})";
else
return $"({(GetFieldType(f, green: false))})Visit(node.{f.Name}) ?? throw new ArgumentNullException(\"{CamelCase(f.Name)}\")";
}
return $"node.{f.Name}";
})));
WriteLine(");");
}
}
CloseBlock();
}
private void WriteRedFactories()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode and not AbstractNode).OfType<Node>().ToList();
WriteLine();
WriteLine("public static partial class SyntaxFactory");
OpenBlock();
foreach (var node in nodes)
{
this.WriteRedFactory(node);
bool skipConvenienceFactories = node.SkipConvenienceFactories != null && string.Compare(node.SkipConvenienceFactories, "true", true) == 0;
if (!skipConvenienceFactories)
{
this.WriteRedFactoryWithNoAutoCreatableTokens(node);
this.WriteRedMinimalFactory(node);
this.WriteRedMinimalFactory(node, withStringNames: true);
}
this.WriteKindConverters(node);
}
CloseBlock();
}
protected bool CanBeAutoCreated(Node node, Field field)
=> IsAutoCreatableToken(node, field) || IsAutoCreatableNode(field);
private bool IsAutoCreatableToken(Node node, Field field)
{
return field.Type == "SyntaxToken"
&& field.Kinds != null
&& ((field.Kinds.Count == 1 && field.Kinds[0].Name != "IdentifierToken" && !field.Kinds[0].Name.EndsWith("LiteralToken", StringComparison.Ordinal)) || (field.Kinds.Count > 1 && field.Kinds.Count == node.Kinds.Count));
}
private bool IsAutoCreatableNode(Field field)
{
var referencedNode = GetNode(field.Type);
return (referencedNode != null && RequiredFactoryArgumentCount(referencedNode) == 0);
}
private bool IsRequiredFactoryField(Node node, Field field)
{
return (!IsOptional(field) && !IsAnyList(field.Type) && !CanBeAutoCreated(node, field)) || IsValueField(field);
}
private bool IsValueField(Field field)
{
return !IsNodeOrNodeList(field.Type);
}
private int RequiredFactoryArgumentCount(Node nd, bool includeKind = true)
{
int count = 0;
// kind must be specified in factory
if (nd.Kinds.Count > 1 && includeKind)
{
count++;
}
foreach (var field in nd.Fields)
{
if (IsRequiredFactoryField(nd, field))
{
count++;
}
}
return count;
}
private int OptionalFactoryArgumentCount(Node nd)
{
int count = 0;
foreach (var field in nd.Fields)
{
if (IsOptional(field) || CanBeAutoCreated(nd, field) || IsAnyList(field.Type))
{
count++;
}
}
return count;
}
// full factory signature with nothing optional
private void WriteRedFactory(Node nd)
{
this.WriteLine();
var valueFields = nd.Fields.Where(n => IsValueField(n)).ToList();
var nodeFields = nd.Fields.Where(n => !IsValueField(n)).ToList();
WriteComment($"<summary>Creates a new {nd.Name} instance.</summary>");
Write($"public static {nd.Name} {StripPost(nd.Name, "Syntax")}(");
WriteRedFactoryParameters(nd);
WriteLine(")");
OpenBlock();
// validate kinds
if (nd.Kinds.Count >= 2)
{
WriteLine("switch (kind)");
OpenBlock();
var kinds = nd.Kinds.Distinct().ToList();
foreach (var kind in kinds)
{
WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds.Last() ? " break;" : "")}");
}
WriteLine("default: throw new ArgumentException(nameof(kind));");
CloseBlock();
}
// validate parameters
foreach (var field in nodeFields)
{
var pname = CamelCase(field.Name);
if (field.Type == "SyntaxToken")
{
var fieldKinds = GetKindsOfFieldOrNearestParent(nd, field);
if (fieldKinds != null && fieldKinds.Count > 0)
{
var kinds = fieldKinds.ToList();
if (IsOptional(field))
{
kinds.Add(new Kind { Name = "None" });
}
if (kinds.Count == 1)
{
WriteLine($"if ({pname}.Kind() != SyntaxKind.{kinds[0].Name}) throw new ArgumentException(nameof({pname}));");
}
else
{
WriteLine($"switch ({pname}.Kind())");
OpenBlock();
foreach (var kind in kinds)
{
WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds.Last() ? " break;" : "")}");
}
WriteLine($"default: throw new ArgumentException(nameof({pname}));");
CloseBlock();
}
}
}
else if (!IsAnyList(field.Type) && !IsOptional(field))
{
WriteLine($"if ({CamelCase(field.Name)} == null) throw new ArgumentNullException(nameof({CamelCase(field.Name)}));");
}
}
Write($"return ({nd.Name})Syntax.InternalSyntax.SyntaxFactory.{StripPost(nd.Name, "Syntax")}(");
Write(CommaJoin(
nd.Kinds.Count > 1 ? "kind" : "",
nodeFields.Select(f =>
{
if (f.Type == "SyntaxToken")
{
if (IsOptional(f))
return $"(Syntax.InternalSyntax.SyntaxToken?){CamelCase(f.Name)}.Node";
else
// We know the GreenNode is not null because it gets a type check earlier in the generated method
return $"(Syntax.InternalSyntax.SyntaxToken){CamelCase(f.Name)}.Node!";
}
else if (f.Type == "SyntaxList<SyntaxToken>")
return $"{CamelCase(f.Name)}.Node.ToGreenList<Syntax.InternalSyntax.SyntaxToken>()";
else if (IsNodeList(f.Type))
return $"{CamelCase(f.Name)}.Node.ToGreenList<Syntax.InternalSyntax.{GetElementType(f.Type)}>()";
else if (IsSeparatedNodeList(f.Type))
return $"{CamelCase(f.Name)}.Node.ToGreenSeparatedList<Syntax.InternalSyntax.{GetElementType(f.Type)}>()";
else if (f.Type == "SyntaxNodeOrTokenList")
return $"{CamelCase(f.Name)}.Node.ToGreenList<Syntax.InternalSyntax.CSharpSyntaxNode>()";
else if (IsOptional(f))
return $"{CamelCase(f.Name)} == null ? null : (Syntax.InternalSyntax.{f.Type}){CamelCase(f.Name)}.Green";
else
return $"(Syntax.InternalSyntax.{f.Type}){CamelCase(f.Name)}.Green";
}),
// values are at end
valueFields.Select(f => CamelCase(f.Name))));
WriteLine(").CreateRed();");
CloseBlock();
}
private void WriteRedFactoryParameters(Node nd)
{
Write(CommaJoin(
nd.Kinds.Count > 1 ? "SyntaxKind kind" : "",
nd.Fields.Select(f => $"{this.GetRedPropertyType(f)} {CamelCase(f.Name)}")));
}
private string GetRedPropertyType(Field field)
{
if (field.Type == "SyntaxList<SyntaxToken>")
return "SyntaxTokenList";
if (IsOptional(field) && IsNode(field.Type) && field.Type != "SyntaxToken")
return field.Type + "?";
return field.Type;
}
private string GetDefaultValue(Node nd, Field field)
{
System.Diagnostics.Debug.Assert(!IsRequiredFactoryField(nd, field));
if (IsOptional(field) || IsAnyList(field.Type))
{
var type = GetRedPropertyType(field);
return type == "SyntaxTokenList" ? "default(SyntaxTokenList)" : "default";
}
else if (field.Type == "SyntaxToken")
{
// auto construct token?
if (field.Kinds.Count == 1)
{
return $"SyntaxFactory.Token(SyntaxKind.{field.Kinds[0].Name})";
}
else
{
return $"SyntaxFactory.Token(Get{StripPost(nd.Name, "Syntax")}{StripPost(field.Name, "Opt")}Kind(kind))";
}
}
else
{
var referencedNode = GetNode(field.Type);
return $"SyntaxFactory.{StripPost(referencedNode.Name, "Syntax")}()";
}
}
// Writes Get<Property>Kind() methods for converting between node kind and member token kinds...
private void WriteKindConverters(Node nd)
{
foreach (var field in nd.Fields)
{
if (field.Type == "SyntaxToken" && CanBeAutoCreated(nd, field) && field.Kinds.Count > 1)
{
WriteLine();
WriteLine($"private static SyntaxKind Get{StripPost(nd.Name, "Syntax")}{StripPost(field.Name, "Opt")}Kind(SyntaxKind kind)");
Indent();
WriteLine("=> kind switch");
OpenBlock();
for (int k = 0; k < field.Kinds.Count; k++)
{
var nKind = nd.Kinds[k];
var pKind = field.Kinds[k];
WriteLine($"SyntaxKind.{nKind.Name} => SyntaxKind.{pKind.Name},");
}
WriteLine("_ => throw new ArgumentOutOfRangeException(),");
CloseBlock(";");
Unindent();
}
}
}
private IEnumerable<Field> DetermineRedFactoryWithNoAutoCreatableTokenFields(Node nd)
{
return nd.Fields.Where(f => !IsAutoCreatableToken(nd, f));
}
// creates a factory without auto-creatable token arguments
private void WriteRedFactoryWithNoAutoCreatableTokens(Node nd)
{
var nAutoCreatableTokens = nd.Fields.Count(f => IsAutoCreatableToken(nd, f));
if (nAutoCreatableTokens == 0)
return; // already handled by general factory
var factoryWithNoAutoCreatableTokenFields = new HashSet<Field>(DetermineRedFactoryWithNoAutoCreatableTokenFields(nd));
var minimalFactoryFields = DetermineMinimalFactoryFields(nd);
if (minimalFactoryFields != null && factoryWithNoAutoCreatableTokenFields.SetEquals(minimalFactoryFields))
{
return; // will be handled in minimal factory case
}
this.WriteLine();
WriteComment($"<summary>Creates a new {nd.Name} instance.</summary>");
Write($"public static {nd.Name} {StripPost(nd.Name, "Syntax")}(");
Write(CommaJoin(
nd.Kinds.Count > 1 ? "SyntaxKind kind" : "",
nd.Fields.Where(factoryWithNoAutoCreatableTokenFields.Contains).Select(
f => $"{GetRedPropertyType(f)} {CamelCase(f.Name)}")));
WriteLine(")");
Write($" => SyntaxFactory.{StripPost(nd.Name, "Syntax")}(");
Write(CommaJoin(
nd.Kinds.Count > 1 ? "kind" : "",
nd.Fields.Select(f => factoryWithNoAutoCreatableTokenFields.Contains(f)
? CamelCase(f.Name)
: GetDefaultValue(nd, f))));
WriteLine(");");
}
private Field DetermineMinimalOptionalField(Node nd)
{
// first if there is a single list, then choose the list because it would not have been optional
int listCount = nd.Fields.Count(f => IsAnyNodeList(f.Type) && !IsAttributeOrModifiersList(f));
if (listCount == 1)
{
return nd.Fields.First(f => IsAnyNodeList(f.Type) && !IsAttributeOrModifiersList(f));
}
else
{
// otherwise, if there is a single optional node, use that..
int nodeCount = nd.Fields.Count(f => IsNode(f.Type) && f.Type != "SyntaxToken");
if (nodeCount == 1)
{
return nd.Fields.First(f => IsNode(f.Type) && f.Type != "SyntaxToken");
}
else
{
return null;
}
}
}
private static bool IsAttributeOrModifiersList(Field f)
{
return f.Name is "AttributeLists" or "Modifiers";
}
private IEnumerable<Field> DetermineMinimalFactoryFields(Node nd)
{
// special case to allow a single optional argument if there would have been no arguments
// and we can determine a best single argument.
Field allowOptionalField = null;
var optionalCount = OptionalFactoryArgumentCount(nd);
if (optionalCount == 0)
{
return null; // no fields...
}
var requiredCount = RequiredFactoryArgumentCount(nd, includeKind: false);
if (requiredCount == 0 && optionalCount > 1)
{
allowOptionalField = DetermineMinimalOptionalField(nd);
}
return nd.Fields.Where(f => IsRequiredFactoryField(nd, f) || allowOptionalField == f);
}
// creates a factory with only the required arguments (everything else is defaulted)
private void WriteRedMinimalFactory(Node nd, bool withStringNames = false)
{
var optionalCount = OptionalFactoryArgumentCount(nd);
if (optionalCount == 0)
return; // already handled w/ general factory method
var minimalFactoryfields = new HashSet<Field>(DetermineMinimalFactoryFields(nd));
if (withStringNames && minimalFactoryfields.Count(f => IsRequiredFactoryField(nd, f) && CanAutoConvertFromString(f)) == 0)
return; // no string-name overload necessary
this.WriteLine();
var hasOptional = minimalFactoryfields.Any(f => !IsRequiredFactoryField(nd, f));
var hasAttributeOrModifiersList = nd.Fields.Any(f => IsAttributeOrModifiersList(f));
if (hasOptional && hasAttributeOrModifiersList)
{
WriteLineWithoutIndent("#pragma warning disable RS0027");
}
WriteComment($"<summary>Creates a new {nd.Name} instance.</summary>");
Write($"public static {nd.Name} {StripPost(nd.Name, "Syntax")}(");
Write(CommaJoin(
nd.Kinds.Count > 1 ? "SyntaxKind kind" : "",
nd.Fields.Where(minimalFactoryfields.Contains).Select(f =>
{
var type = GetRedPropertyType(f);
if (IsRequiredFactoryField(nd, f))
{
if (withStringNames && CanAutoConvertFromString(f))
type = "string";
return $"{type} {CamelCase(f.Name)}";
}
else
{
if (IsNode(f.Type) && !IsOptional(f) && f.Type != "SyntaxToken")
type += "?";
return $"{type} {CamelCase(f.Name)} = default";
}
})));
WriteLine(")");
Write($" => SyntaxFactory.{StripPost(nd.Name, "Syntax")}(");
Write(CommaJoin(
nd.Kinds.Count > 1 ? "kind" : "",
nd.Fields.Select(f =>
{
if (minimalFactoryfields.Contains(f))
{
if (IsRequiredFactoryField(nd, f))
{
if (withStringNames && CanAutoConvertFromString(f))
return $"{GetStringConverterMethod(f)}({CamelCase(f.Name)})";
else
return CamelCase(f.Name);
}
else
{
if (IsOptional(f) || IsAnyList(f.Type))
return CamelCase(f.Name);
else
return $"{CamelCase(f.Name)} ?? {GetDefaultValue(nd, f)}";
}
}
return GetDefaultValue(nd, f);
})));
WriteLine(");");
if (hasOptional && hasAttributeOrModifiersList)
{
WriteLineWithoutIndent("#pragma warning restore RS0027");
}
}
private bool CanAutoConvertFromString(Field field)
{
return IsIdentifierToken(field) || IsIdentifierNameSyntax(field);
}
private bool IsIdentifierToken(Field field)
{
return field.Type == "SyntaxToken" && field.Kinds != null && field.Kinds.Count == 1 && field.Kinds[0].Name == "IdentifierToken";
}
private bool IsIdentifierNameSyntax(Field field)
{
return field.Type == "IdentifierNameSyntax";
}
private string GetStringConverterMethod(Field field)
{
if (IsIdentifierToken(field))
{
return "SyntaxFactory.Identifier";
}
else if (IsIdentifierNameSyntax(field))
{
return "SyntaxFactory.IdentifierName";
}
else
{
throw new NotSupportedException();
}
}
/// <summary>
/// Anything inside a <Comment> tag gets written out (escaping untouched) as the
/// XML doc comment. Line breaks will be preserved.
/// </summary>
private void WriteComment(string comment)
{
if (comment != null)
{
var lines = comment.Split(new string[] { "\r", "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines.Where(l => !string.IsNullOrWhiteSpace(l)))
{
WriteLine($"/// {line.TrimStart()}");
}
}
}
/// <summary>
/// Anything inside a <Comment> tag gets written out (escaping untouched) as the
/// XML doc comment. Line breaks will be preserved.
/// </summary>
private void WriteComment(Comment comment, string indent)
{
if (comment != null)
{
foreach (XmlElement element in comment.Body)
{
string[] lines = element.OuterXml.Split(new string[] { "\r", "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines.Where(l => !string.IsNullOrWhiteSpace(l)))
{
WriteLine($"{indent}/// {line.TrimStart()}");
}
}
}
}
}
}
|