|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
namespace RazorSyntaxGenerator;
internal class SourceWriter : AbstractFileWriter
{
private SourceWriter(TextWriter writer, Tree tree)
: base(writer, tree)
{
}
public static void WriteMain(TextWriter writer, Tree tree) => new SourceWriter(writer, tree).WriteMain();
public static void WriteInternal(TextWriter writer, Tree tree) => new SourceWriter(writer, tree).WriteInternal();
public static void WriteSyntax(TextWriter writer, Tree tree) => new SourceWriter(writer, tree).WriteSyntax();
private void WriteFileHeader()
{
WriteLine("// <auto-generated />");
WriteLine();
WriteLine("using System;");
WriteLine("using System.Collections.Generic;");
WriteLine("using Microsoft.AspNetCore.Razor.Language.Legacy;");
WriteLine();
}
private void WriteInternal()
{
WriteFileHeader();
WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;");
WriteGreenTypes();
WriteGreenVisitors();
WriteGreenRewriter();
WriteStaticGreenFactories();
}
private void WriteSyntax()
{
WriteFileHeader();
WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax;");
WriteRedTypes();
}
private void WriteMain()
{
WriteFileHeader();
WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax;");
WriteRedVisitors();
WriteRedRewriter();
WriteRedFactories();
}
private void WriteGreenTypes()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList();
foreach (var node in nodes)
{
WriteLine();
WriteGreenType(node);
}
}
private void WriteGreenType(TreeType greenType)
{
WriteComment(greenType.TypeComment);
switch (greenType)
{
case AbstractNode abstractNode:
{
WriteLine($"internal abstract partial class {abstractNode.Name} : {(abstractNode.Base == "SyntaxNode" ? "GreenNode" : abstractNode.Base)}");
using (Block())
{
// ctor with diagnostics
WriteLine($"internal {abstractNode.Name}(SyntaxKind kind, RazorDiagnostic[] diagnostics)");
WriteIndentedLine(": base(kind, diagnostics)");
using (Block())
{
if (abstractNode.Name == "DirectiveTriviaSyntax")
{
WriteLine("_flags |= NodeFlags.ContainsDirectives;");
}
}
WriteLine();
// ctor without diagnostics
WriteLine($"internal {abstractNode.Name}(SyntaxKind kind)");
WriteIndentedLine(": base(kind)");
using (Block())
{
if (abstractNode.Name == "DirectiveTriviaSyntax")
{
WriteLine("_flags |= NodeFlags.ContainsDirectives;");
}
}
/* Remove
// object reader constructor
WriteLine();
WriteLine(" protected {0}(ObjectReader reader)", node.Name);
WriteLine(" : base(reader)");
WriteLine(" {");
if (node.Name == "DirectiveTriviaSyntax")
{
WriteLine(" _flags |= NodeFlags.ContainsDirectives;");
}
WriteLine(" }"); */
var valueFields = abstractNode.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = abstractNode.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();
for (int i = 0, n = nodeFields.Count; i < n; i++)
{
var field = nodeFields[i];
if (IsNodeOrNodeList(field.Type))
{
WriteLine();
WriteComment(field.PropertyComment);
if (IsSeparatedNodeList(field.Type) ||
IsNodeList(field.Type))
{
WriteLine($"public abstract {(IsNew(field) ? "new " : "")}{field.Type} {field.Name} {{ get; }}");
}
else
{
WriteLine($"public abstract {(IsNew(field) ? "new " : "")}{field.Type} {field.Name} {{ get; }}");
}
}
}
for (int i = 0, n = valueFields.Count; i < n; i++)
{
var field = valueFields[i];
WriteLine();
WriteComment(field.PropertyComment);
WriteLine($"public abstract {(IsNew(field) ? "new " : "")}{field.Type} {field.Name} {{ get; }}");
}
}
break;
}
case Node node:
{
WriteLine($"internal sealed partial class {node.Name} : {node.Base}");
using (Block())
{
var valueFields = node.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = node.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();
for (int i = 0, n = nodeFields.Count; i < n; i++)
{
var field = nodeFields[i];
var type = GetFieldType(field, green: true);
WriteLine($"internal readonly {type} {GetFieldName(field)};");
}
for (int i = 0, n = valueFields.Count; i < n; i++)
{
var field = valueFields[i];
WriteLine($"internal readonly {field.Type} {GetFieldName(field)};");
}
// write constructor with diagnostics
WriteLine();
Write($"internal {node.Name}(SyntaxKind kind");
WriteGreenNodeConstructorArgs(nodeFields, valueFields);
WriteLine(", RazorDiagnostic[] diagnostics)");
WriteIndentedLine(": base(kind, diagnostics)");
using (Block())
{
WriteCtorBody(valueFields, nodeFields);
}
/* Remove
// write constructor with async
WriteLine();
Write(" internal {0}(SyntaxKind kind", node.Name);
WriteGreenNodeConstructorArgs(nodeFields, valueFields);
WriteLine(", SyntaxFactoryContext context)");
WriteLine(" : base(kind)");
WriteLine(" {");
WriteLine(" this.SetFactoryContext(context);");
WriteCtorBody(valueFields, nodeFields);
WriteLine(" }");
WriteLine(); */
// write constructor without diagnostics
WriteLine();
Write($"internal {node.Name}(SyntaxKind kind");
WriteGreenNodeConstructorArgs(nodeFields, valueFields);
WriteLine(")");
WriteIndentedLine(": base(kind)");
using (Block())
{
WriteCtorBody(valueFields, nodeFields);
}
WriteLine();
// property accessors
foreach (var field in nodeFields)
{
var type = field.Type;
WriteComment(field.PropertyComment);
if (IsNodeList(type))
{
WriteLine($"public {OverrideOrNewModifier(field)}{type} {field.Name} => new {type}({GetFieldName(field)});");
}
else if (IsSeparatedNodeList(type))
{
WriteLine($"public {OverrideOrNewModifier(field)}{type} {field.Name} => new {type}(new SyntaxList<GreenNode>({GetFieldName(field)}));");
}
else if (type == "SyntaxNodeOrTokenList")
{
WriteLine($"public {OverrideOrNewModifier(field)}SyntaxList<GreenNode> {field.Name} => new SyntaxList<GreenNode>({GetFieldName(field)});");
}
else
{
WriteLine($"public {OverrideOrNewModifier(field)}{type} {field.Name} => {GetFieldName(field)};");
}
}
foreach (var field in valueFields)
{
WriteComment(field.PropertyComment);
WriteLine($"public {OverrideOrNewModifier(field)}{field.Type} {field.Name} => {GetFieldName(field)};");
}
// GetSlot
WriteLine();
Write("internal override GreenNode GetSlot(int index)");
if (nodeFields.Count == 0)
{
WriteLine(" => null;");
}
else if (nodeFields.Count == 1)
{
WriteLine();
WriteIndentedLine($"=> index == 0 ? this.{GetFieldName(nodeFields[0])} : null;");
}
else
{
WriteLine();
using (Indent())
{
WriteLine("=> index switch");
using (Block(addSemicolon: true))
{
for (int i = 0, n = nodeFields.Count; i < n; i++)
{
var field = nodeFields[i];
WriteLine($"{i} => {GetFieldName(field)},");
}
WriteLine("_ => null");
}
}
}
WriteLine();
WriteLine($"internal override SyntaxNode CreateRed(SyntaxNode parent, int position) => new Syntax.{node.Name}(this, parent, position);");
WriteGreenAcceptMethods(node);
WriteGreenUpdateMethod(node);
WriteSetDiagnostics(node);
}
break;
}
}
}
private void WriteGreenNodeConstructorArgs(List<Field> nodeFields, List<Field> valueFields)
{
foreach (var field in nodeFields)
{
var type = GetFieldType(field, green: true);
Write($", {type} {GetParameterName(field)}");
}
foreach (var field in valueFields)
{
Write($", {field.Type} {GetParameterName(field)}");
}
}
private void WriteCtorBody(List<Field> valueFields, List<Field> nodeFields)
{
// constructor body
WriteLine("SlotCount = {0};", nodeFields.Count);
foreach (var field in nodeFields)
{
if (IsAnyList(field.Type) || IsOptional(field))
{
WriteLine($"if ({GetParameterName(field)} != null)");
using (Block())
{
WriteLine($"AdjustFlagsAndWidth({GetParameterName(field)});");
WriteLine($"{GetFieldName(field)} = {GetParameterName(field)};");
}
}
else
{
WriteLine($"AdjustFlagsAndWidth({GetParameterName(field)});");
WriteLine($"{GetFieldName(field)} = {GetParameterName(field)};");
}
}
foreach (var field in valueFields)
{
WriteLine($"{GetFieldName(field)} = {GetParameterName(field)};");
}
}
private void WriteSetDiagnostics(Node node)
{
WriteLine();
WriteLine("internal override GreenNode SetDiagnostics(RazorDiagnostic[] diagnostics)");
using (Indent())
{
Write($"=> new {node.Name}(");
WriteCommaSeparatedList(
"Kind",
node.Fields.Select(GetFieldName),
"diagnostics");
WriteLine(");");
}
}
private void WriteGreenAcceptMethods(Node node)
{
WriteLine();
WriteLine($"public override TResult Accept<TResult>(SyntaxVisitor<TResult> visitor) => visitor.Visit{StripPost(node.Name, "Syntax")}(this);");
WriteLine($"public override void Accept(SyntaxVisitor 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 SyntaxVisitor" + (withResult ? "<TResult>" : ""));
using (Block())
{
foreach (var node in nodes.OfType<Node>())
{
WriteLine($"public virtual {(withResult ? "TResult" : "void")} Visit{StripPost(node.Name, "Syntax")}({node.Name} node) => DefaultVisit(node);");
}
}
}
private void WriteGreenUpdateMethod(Node node)
{
WriteLine();
Write($"public {node.Name} Update(");
// parameters
WriteCommaSeparatedList(node.Fields.Select(static f =>
{
var type =
f.Type == "SyntaxNodeOrTokenList" ? "InternalSyntax.SyntaxList<GreenNode>" :
f.Type == "SyntaxTokenList" ? "InternalSyntax.SyntaxList<SyntaxToken>" :
IsNodeList(f.Type) ? "InternalSyntax." + f.Type :
IsSeparatedNodeList(f.Type) ? "InternalSyntax." + f.Type :
f.Type;
return $"{type} {GetParameterName(f)}";
}));
WriteLine(")");
using (Block())
{
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($"{GetParameterName(field)} != {field.Name}");
nCompared++;
}
}
if (nCompared > 0)
{
WriteLine(")");
using (Block())
{
Write($"var newNode = SyntaxFactory.{StripPost(node.Name, "Syntax")}(");
WriteCommaSeparatedList(
node.Kinds.Count > 1 ? "Kind" : "",
node.Fields.Select(GetParameterName));
WriteLine(");");
WriteLine("var diags = GetDiagnostics();");
WriteLine("if (diags != null && diags.Length > 0)");
WriteIndentedLine("newNode = newNode.WithDiagnosticsGreen(diags);");
WriteLine("return newNode;");
}
}
WriteLine();
WriteLine("return this;");
}
}
private void WriteGreenRewriter()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList();
WriteLine();
WriteLine("internal partial class SyntaxRewriter : SyntaxVisitor<GreenNode>");
using (Block())
{
var 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 GreenNode Visit{StripPost(node.Name, "Syntax")}({node.Name} node)");
using (Indent())
{
if (nodeFields.Count == 0)
{
WriteLine("=> node;");
}
else
{
Write("=> node.Update(");
WriteCommaSeparatedList(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(");");
}
}
}
}
}
private void WriteStaticGreenFactories()
{
var nodes = Tree.Types.Where(n => n is not (PredefinedNode or AbstractNode)).ToList();
WriteLine();
WriteLine("internal static partial class SyntaxFactory");
using (Block())
{
WriteGreenFactories(nodes);
WriteGreenTypeList();
}
}
private void WriteGreenFactories(List<TreeType> nodes, bool withSyntaxFactoryContext = false)
{
for (int i = 0, n = nodes.Count; i < n; i++)
{
var node = nodes[i];
WriteGreenFactory((Node)node, withSyntaxFactoryContext);
if (i < n - 1)
{
WriteLine();
}
}
}
private void WriteGreenTypeList()
{
WriteLine();
WriteLine("internal static IEnumerable<Type> GetNodeTypes()");
using (Block())
{
WriteLine("return new Type[]");
using (Block(addSemicolon: true))
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode and not AbstractNode).ToList();
for (int i = 0, n = nodes.Count; i < n; i++)
{
var node = nodes[i];
Write($"typeof({node.Name})");
if (i < n - 1)
{
Write(",");
}
WriteLine();
}
}
}
}
private void WriteGreenFactory(Node node, bool withSyntaxFactoryContext = false)
{
var valueFields = node.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = node.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();
Write($"public {(withSyntaxFactoryContext ? "" : "static ")}{node.Name} {StripPost(node.Name, "Syntax")}(");
WriteGreenFactoryParameters(node);
WriteLine(")");
using (Block())
{
// validate kind
if (node.Kinds.Count > 1)
{
WriteLine("switch (kind)");
using (Block())
{
var kinds = node.Kinds.Distinct().ToList();
foreach (var kind in node.Kinds)
{
WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds[^1] ? " break;" : string.Empty)}");
}
WriteLine("default: throw new ArgumentException(\"kind\");");
}
}
// validate parameters
//WriteLine("#if DEBUG");
foreach (var field in nodeFields)
{
var pname = GetParameterName(field);
if (!IsAnyList(field.Type) && !IsOptional(field))
{
WriteLine($"ArgHelper.ThrowIfNull({pname});");
}
if (field.Type == "SyntaxToken" && field.Kinds?.Count > 0)
{
if (field.Kinds.Count == 1)
{
if (IsOptional(field))
{
WriteLine($"if ({pname} is not null && {pname}.Kind is not (SyntaxKind.{field.Kinds[0].Name} or SyntaxKind.None))");
}
else
{
WriteLine($"if ({pname}.Kind != SyntaxKind.{field.Kinds[0].Name})");
}
WriteIndentedLine($"ThrowHelper.ThrowArgumentException(nameof({pname}), " +
$"$\"Invalid SyntaxKind. Expected 'SyntaxKind.{field.Kinds[0].Name}'{(IsOptional(field) ? " or 'SyntaxKind.None'" : "")}, but it was {{{pname}.Kind}}\");");
}
else
{
if (IsOptional(field))
{
WriteLine($"if ({pname} != null)");
OpenBlock();
}
WriteLine($"switch ({pname}.Kind)");
using (Block())
{
foreach (var kind in field.Kinds)
{
WriteLine($"case SyntaxKind.{kind.Name}:");
}
//we need to check for Kind=None as well as node == null because that's what the red factory will pass
if (IsOptional(field))
{
WriteLine("case SyntaxKind.None:");
}
WriteIndentedLine("break;");
WriteLine("default:");
WriteIndentedLine($"throw new ArgumentException(\"{pname}\");");
}
if (IsOptional(field))
{
CloseBlock();
}
}
}
}
//WriteLine("#endif");
if (node.Name != "SkippedTokensTriviaSyntax" &&
node.Name != "DocumentationCommentTriviaSyntax" &&
node.Name != "IncompleteMemberSyntax" &&
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();
/* Remove
//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 ({0})cached;", nd.Name);
WriteLine(); */
//var result = new IdentifierNameSyntax(SyntaxKind.IdentifierName, identifier);
Write($"var result = new {node.Name}(");
WriteCtorArgList(node, withSyntaxFactoryContext, valueFields, nodeFields);
WriteLine(");");
/* Remove
//if (hash >= 0)
WriteLine(" if (hash >= 0)");
//{
WriteLine(" {");
// SyntaxNodeCache.AddNode(result, hash);
WriteLine(" SyntaxNodeCache.AddNode(result, hash);");
//}
WriteLine(" }"); */
WriteLine();
//return result;
WriteLine("return result;");
}
else
{
WriteLine();
Write("return new {0}(", node.Name);
WriteCtorArgList(node, withSyntaxFactoryContext, valueFields, nodeFields);
WriteLine(");");
}
}
}
private void WriteGreenFactoryParameters(Node nd)
{
if (nd.Kinds.Count > 1)
{
Write("SyntaxKind kind, ");
}
for (int i = 0, n = nd.Fields.Count; i < n; i++)
{
var field = nd.Fields[i];
if (i > 0)
{
Write(", ");
}
var type = field.Type;
if (type == "SyntaxNodeOrTokenList")
{
type = "SyntaxList<GreenNode>";
}
else if (IsSeparatedNodeList(field.Type) ||
IsNodeList(field.Type))
{
type = "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax." + type;
}
Write($"{type} {GetParameterName(field)}");
}
}
private void WriteCtorArgList(Node nd, bool withSyntaxFactoryContext, List<Field> valueFields, List<Field> nodeFields)
{
if (nd.Kinds.Count == 1)
{
Write("SyntaxKind.");
Write(nd.Kinds[0].Name);
}
else
{
Write("kind");
}
for (int i = 0, n = nodeFields.Count; i < n; i++)
{
var field = nodeFields[i];
Write(", ");
if (field.Type == "SyntaxList<SyntaxToken>" || IsAnyList(field.Type))
{
Write($"{GetParameterName(field)}.Node");
}
else
{
Write(GetParameterName(field));
}
}
// values are at end
for (int i = 0, n = valueFields.Count; i < n; i++)
{
var field = valueFields[i];
Write(", ");
Write(GetParameterName(field));
}
if (withSyntaxFactoryContext)
{
Write(", this.context");
}
}
private void WriteRedTypes()
{
var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList();
for (int i = 0, n = nodes.Count; i < n; i++)
{
var node = nodes[i];
WriteLine();
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 redType)
{
WriteComment(redType.TypeComment);
switch (redType)
{
case AbstractNode abstractNode:
{
WriteLine($"internal abstract partial class {abstractNode.Name} : {abstractNode.Base}");
using (Block())
{
WriteLine($"internal {abstractNode.Name}(GreenNode green, SyntaxNode parent, int position)");
WriteIndentedLine(": base(green, parent, position)");
using (Block())
{
}
var valueFields = abstractNode.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = GetNodeOrNodeListFields(abstractNode);
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 {abstractNode.Name} With{field.Name}({fieldType} {GetParameterName(field)}) => With{field.Name}Core({GetParameterName(field)});");
WriteLine($"internal abstract {abstractNode.Name} With{field.Name}Core({fieldType} {GetParameterName(field)});");
if (IsAnyList(field.Type))
{
var argType = GetElementType(field.Type);
WriteLine();
WriteLine($"public {abstractNode.Name} Add{field.Name}(params {argType}[] items) => Add{field.Name}Core(items);");
WriteLine($"internal abstract {abstractNode.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 {abstractNode.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items) => Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}Core(items);");
WriteLine($"internal abstract {abstractNode.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(abstractNode.Base);
if (baseType != null)
{
var baseNodeFields = GetNodeOrNodeListFields(baseType);
if (baseNodeFields.Count > 0)
{
WriteLine();
}
foreach (var baseField in baseNodeFields)
{
WriteLine($"public new {abstractNode.Name} With{baseField.Name}({GetRedFieldType(baseField)} {GetParameterName(baseField)}) " +
$"=> ({abstractNode.Name})With{baseField.Name}Core({GetParameterName(baseField)});");
}
foreach (var baseField in baseNodeFields)
{
if (IsAnyList(baseField.Type))
{
var argType = GetElementType(baseField.Type);
WriteLine();
WriteLine($"public new {abstractNode.Name} Add{baseField.Name}(params {argType}[] items) => ({abstractNode.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);");
}
}
}
}
}
}
}
break;
}
case Node node:
{
WriteLine($"internal sealed partial class {node.Name} : {node.Base}");
using (Block())
{
var valueFields = node.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
var nodeFields = node.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();
foreach (var field in nodeFields)
{
if (field.Type != "SyntaxToken" && field.Type != "SyntaxList<SyntaxToken>")
{
if (IsSeparatedNodeList(field.Type) || field.Type == "SyntaxNodeOrTokenList")
{
WriteLine($"private SyntaxNode {GetFieldName(field)};");
}
else
{
var type = GetFieldType(field, green: false);
WriteLine($"private {type} {GetFieldName(field)};");
}
}
}
// write constructor
WriteLine();
WriteLine($"internal {node.Name}(GreenNode green, SyntaxNode parent, int position)");
WriteIndentedLine(": base(green, parent, position)");
using (Block())
{
}
WriteLine();
// property accessors
for (var i = 0; i < nodeFields.Count; i++)
{
var field = nodeFields[i];
if (field.Type == "SyntaxToken")
{
WriteComment(field.PropertyComment);
Write($"public {OverrideOrNewModifier(field)}{field.Type} {field.Name}");
if (IsOptional(field))
{
WriteLine();
using (Block())
{
WriteLine("get");
using (Block())
{
WriteLine($"var slot = ((InternalSyntax.{node.Name})Green).{field.Name};");
WriteLine($"return slot != null ? new SyntaxToken(this, slot, {GetChildPosition(i)}, {GetChildIndex(i)}) : default;");
}
}
}
else
{
WriteLine($" => new SyntaxToken(this, ((InternalSyntax.{node.Name})Green).{GetFieldName(field)}, {GetChildPosition(i)}, {GetChildIndex(i)});");
}
}
else if (field.Type == "SyntaxList<SyntaxToken>")
{
WriteComment(field.PropertyComment);
WriteLine($"public {OverrideOrNewModifier(field)}SyntaxTokenList {field.Name}");
using (Block())
{
WriteLine(" get");
using (Block())
{
WriteLine($"var slot = Green.GetSlot({i});");
WriteLine($"return slot != null ? new SyntaxTokenList(this, slot, {GetChildPosition(i)}, {GetChildIndex(i)}) : default;");
}
}
}
else
{
WriteComment(field.PropertyComment);
Write($"public {OverrideOrNewModifier(field)}{field.Type} {field.Name} ");
if (IsNodeList(field.Type))
{
WriteLine($" => new {field.Type}(GetRed(ref {GetFieldName(field)}, {i}));");
}
else if (IsSeparatedNodeList(field.Type))
{
WriteLine();
using (Block())
{
WriteLine("get");
using (Block())
{
WriteLine($"var red = GetRed(ref {GetFieldName(field)}, {i});");
WriteLine($"return red != null ? new {field.Type}(red, {GetChildIndex(i)}) : default;");
}
}
}
else if (field.Type == "SyntaxNodeOrTokenList")
{
throw new InvalidOperationException("field cannot be a random SyntaxNodeOrTokenList");
}
else
{
if (i == 0)
{
WriteLine($" => GetRedAtZero(ref {GetFieldName(field)});");
}
else
{
WriteLine($" => GetRed(ref {GetFieldName(field)}, {i});");
}
}
}
}
foreach (var field in valueFields)
{
WriteComment(field.PropertyComment);
WriteLine($"public {OverrideOrNewModifier(field)}{field.Type} {field.Name} => ((InternalSyntax.{node.Name})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>")
.ToList();
if (relevantNodes.Count == 0)
{
WriteLine(" => null;");
}
else if (relevantNodes.Count == 1)
{
var (field, index) = relevantNodes[0];
var whenTrue = index == 0
? $"GetRedAtZero(ref this.{GetFieldName(field)})"
: $"GetRed(ref this.{GetFieldName(field)}, {index})";
WriteLine($" => index == {index} ? {whenTrue} : null;");
}
else
{
WriteLine();
using (Indent())
{
WriteLine("=> index switch");
using (Block(addSemicolon: true))
{
foreach (var (field, index) in relevantNodes)
{
if (index == 0)
{
WriteLine($"{index} => GetRedAtZero(ref {GetFieldName(field)}),");
}
else
{
WriteLine($"{index} => GetRed(ref {GetFieldName(field)}, {index}),");
}
}
WriteLine("_ => null");
}
}
}
}
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>")
.ToList();
if (relevantNodes.Count == 0)
{
WriteLine(" => null;");
}
else if (relevantNodes.Count == 1)
{
var (field, index) = relevantNodes[0];
WriteLine($" => index == {index} ? this.{GetFieldName(field)} : null;");
}
else
{
WriteLine();
using (Indent())
{
WriteLine("=> index switch");
using (Block(addSemicolon: true))
{
foreach (var (field, index) in relevantNodes)
{
WriteLine($"{index} => this.{GetFieldName(field)},");
}
WriteLine("_ => null");
}
}
}
}
WriteRedAcceptMethods(node);
WriteRedUpdateMethod(node);
WriteRedWithMethods(node);
WriteRedListHelperMethods(node);
}
break;
}
}
}
private static string GetRedFieldType(Field field)
{
return field.Type == "SyntaxList<SyntaxToken>"
? "SyntaxTokenList"
: field.Type;
}
private static string GetChildPosition(int i)
{
return i == 0 ? "Position" : $"GetChildPosition({i})";
}
private static string GetChildIndex(int i)
{
return i == 0 ? "0" : $"GetChildIndex({i})";
}
private void WriteRedAcceptMethods(Node node)
{
WriteLine();
WriteLine($"public override TResult Accept<TResult>(SyntaxVisitor<TResult> visitor) => visitor.Visit{StripPost(node.Name, "Syntax")}(this);");
WriteLine($"public override void Accept(SyntaxVisitor visitor) => visitor.Visit{StripPost(node.Name, "Syntax")}(this);");
}
private void WriteRedVisitors()
{
WriteRedVisitor(genericResult: true);
WriteRedVisitor(genericResult: false);
}
private void WriteRedVisitor(bool genericResult)
{
var (result, typeParams) = genericResult
? ("TResult", "<TResult>")
: ("void", string.Empty);
var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList();
WriteLine();
WriteLine($"internal partial class SyntaxVisitor{typeParams}");
using (Block())
{
var 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 {result} Visit{StripPost(node.Name, "Syntax")}({node.Name} node) => DefaultVisit(node);");
}
}
}
private void WriteRedUpdateMethod(Node node)
{
WriteLine();
Write($"public {node.Name} Update(");
WriteCommaSeparatedList(
node.Fields.Select(f => $"{GetRedPropertyType(f)} {GetParameterName(f)}"));
WriteLine(")");
using (Block())
{
Write("if (");
var nCompared = 0;
foreach (var field in node.Fields)
{
if (IsDerivedOrListOfDerived("SyntaxNode", field.Type) ||
IsDerivedOrListOfDerived("SyntaxToken", field.Type) ||
field.Type is "SyntaxNodeOrTokenList" or "ISpanChunkGenerator" or "SpanEditHandler" or "DirectiveDescriptor")
{
if (nCompared > 0)
{
Write(" || ");
}
Write($"{GetParameterName(field)} != {field.Name}");
nCompared++;
}
}
if (nCompared > 0)
{
WriteLine(")");
using (Block())
{
Write($"var newNode = SyntaxFactory.{StripPost(node.Name, "Syntax")}(");
WriteCommaSeparatedList(
node.Kinds.Count > 1 ? "Kind" : string.Empty,
node.Fields.Select(f => GetParameterName(f)));
WriteLine(");");
WriteLine("var diagnostics = GetDiagnostics();");
WriteLine("if (diagnostics != null && diagnostics.Length > 0)");
WriteIndentedLine("newNode = newNode.WithDiagnostics(diagnostics);");
WriteLine("return newNode;");
}
}
WriteLine();
WriteLine("return this;");
}
}
private void WriteRedWithMethods(Node node)
{
foreach (var field in node.Fields)
{
var type = GetRedPropertyType(field);
if (field == node.Fields.First())
{
WriteLine();
}
var isNew = false;
if (IsOverride(field))
{
var baseType = GetHighestBaseTypeWithField(node, field.Name);
if (baseType != null)
{
WriteLine($"internal override {baseType.Name} With{field.Name}Core({type} {GetParameterName(field)}) => With{field.Name}({GetParameterName(field)});");
isNew = true;
}
}
Write($"public{(isNew ? " new " : " ")}{node.Name} With{StripPost(field.Name, "Opt")}({type} {GetParameterName(field)}) => Update(");
// call update inside each setter
WriteCommaSeparatedList(node.Fields.Select(f =>
f == field ? GetParameterName(f) : f.Name));
WriteLine(");");
}
}
private TreeType GetHighestBaseTypeWithField(TreeType node, string name)
{
TreeType bestType = 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;
}
}
return bestType;
}
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)
{
foreach (var field in node.Fields)
{
if (IsAnyList(field.Type))
{
// 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))
{
WriteRedNestedListHelperMethods(node, field, referencedNode, referencedNodeField);
}
}
}
}
}
}
private Node TryGetNodeForNestedList(Field field)
{
var 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 = GetHighestBaseTypeWithField(node, field.Name);
if (baseType != null)
{
WriteLine($"internal override {baseType.Name} Add{field.Name}Core(params {argType}[] items) => Add{field.Name}(items);");
isNew = true;
}
}
WriteLine();
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
WriteLine();
Write($"public{(isNew ? " new " : " ")}{node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items)");
if (IsOptional(field))
{
WriteLine();
using (Block())
{
var factoryName = StripPost(referencedNode.Name, "Syntax");
var varName = StripPost(GetFieldName(field), "Opt");
WriteLine($"var {varName} = this.{field.Name} ?? SyntaxFactory.{factoryName}();");
WriteLine($"return this.With{StripPost(field.Name, "Opt")}({varName}.With{StripPost(referencedNodeField.Name, "Opt")}({varName}.{referencedNodeField.Name}.AddRange(items)));");
}
}
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("internal partial class SyntaxRewriter : SyntaxVisitor<SyntaxNode>");
using (Block())
{
var 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 SyntaxNode Visit{StripPost(node.Name, "Syntax")}({node.Name} node)");
if (node.Fields.Count == 0)
{
WriteIndentedLine("=> node;");
}
else
{
using (Indent())
{
Write("=> node.Update(");
WriteCommaSeparatedList(node.Fields.Select(f =>
{
if (IsNodeOrNodeList(f.Type))
{
if (IsAnyList(f.Type))
{
return $"VisitList(node.{f.Name})";
}
else if (f.Type == "SyntaxToken")
{
return $"({f.Type})VisitToken(node.{f.Name})";
}
else
{
return $"({f.Type})Visit(node.{f.Name})";
}
}
return $"node.{f.Name}";
}));
WriteLine(");");
}
}
}
}
}
private void WriteRedFactories()
{
var nodes = Tree.Types.Where(n => n is not PredefinedNode and not AbstractNode).OfType<Node>().ToList();
WriteLine();
WriteLine("internal static partial class SyntaxFactory");
using (Block())
{
foreach (var node in nodes)
{
WriteRedFactory(node);
WriteRedFactoryWithNoAutoCreatableTokens(node);
WriteRedMinimalFactory(node);
WriteRedMinimalFactory(node, withStringNames: true);
WriteKindConverters(node);
}
}
}
protected bool CanBeAutoCreated(Node node, Field field)
{
return IsAutoCreatableToken(node, field) || IsAutoCreatableNode(node, field);
}
private static 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(Node node, 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) && field.Type != "DirectiveDescriptor";
}
private int RequiredFactoryArgumentCount(Node nd, bool includeKind = true)
{
var count = 0;
// kind must be specified in factory
if (nd.Kinds.Count > 1 && includeKind)
{
count++;
}
for (int i = 0, n = nd.Fields.Count; i < n; i++)
{
var field = nd.Fields[i];
if (IsRequiredFactoryField(nd, field))
{
count++;
}
}
return count;
}
private int OptionalFactoryArgumentCount(Node nd)
{
var count = 0;
for (int i = 0, n = nd.Fields.Count; i < n; i++)
{
var field = nd.Fields[i];
if (IsOptional(field) || CanBeAutoCreated(nd, field) || IsAnyList(field.Type))
{
count++;
}
}
return count;
}
// full factory signature with nothing optional
private void WriteRedFactory(Node node)
{
WriteLine();
var valueFields = node.Fields.Where(IsValueField).ToList();
var nodeFields = node.Fields.Where(n => !IsValueField(n)).ToList();
var hasValidation = node.Kinds.Count > 1 || nodeFields.Any(f => NeedsNullCheck(f) || NeedsSyntaxKindValidation(f));
WriteComment($"<summary>Creates a new {node.Name} instance.</summary>");
Write($"public static {node.Name} {StripPost(node.Name, "Syntax")}(");
WriteCommaSeparatedList(
node.Kinds.Count > 1 ? "SyntaxKind kind" : string.Empty,
node.Fields.Select(f => $"{GetRedPropertyType(f)} {GetParameterName(f)}"));
WriteLine(")");
if (hasValidation)
{
// We only need to write a block if we're adding validation.
OpenBlock();
// validate kinds
if (node.Kinds.Count > 1)
{
WriteLine("switch (kind)");
using (Block())
{
var kinds = node.Kinds.Distinct().ToList();
foreach (var kind in node.Kinds)
{
WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds[^1] ? " break;" : string.Empty)}");
}
WriteLine("default: throw new ArgumentException(\"kind\");");
}
}
// validate parameters
foreach (var field in nodeFields)
{
var pname = GetParameterName(field);
if (field.Type == "SyntaxToken" && field.Kinds?.Count > 0)
{
var fieldKinds = GetKindsOfFieldOrNearestParent(node, field);
if (fieldKinds.Count > 0)
{
var kinds = fieldKinds.ToList();
if (IsOptional(field))
{
kinds.Add(new Kind { Name = "None" });
}
var kindText = string.Join(" or ", kinds.Select(k => $"SyntaxKind.{k.Name}"));
WriteLine($"if ({pname}.Kind is not ({kindText})) return ThrowHelper.ThrowArgumentException<{node.Name}>(nameof({pname}), " +
$"$\"Invalid SyntaxKind. Expected '{kindText}', but it was {{{pname}.Kind}}\");");
}
}
else if (!IsAnyList(field.Type) && !IsOptional(field))
{
WriteLine($"ArgHelper.ThrowIfNull({pname});");
}
}
Write($"return ({node.Name})InternalSyntax.SyntaxFactory.{StripPost(node.Name, "Syntax")}(");
}
else
{
// If we're not generating validation, we can use an expression body.
IncreaseIndent();
Write($"=> ({node.Name})InternalSyntax.SyntaxFactory.{StripPost(node.Name, "Syntax")}(");
}
WriteCommaSeparatedList(
node.Kinds.Count > 1 ? "kind" : string.Empty,
nodeFields.Select(f =>
{
if (f.Type == "SyntaxToken")
{
return $"(Syntax.InternalSyntax.SyntaxToken){GetParameterName(f)}.Node";
}
else if (f.Type == "SyntaxList<SyntaxToken>")
{
return $"{GetParameterName(f)}.Node.ToGreenList<InternalSyntax.SyntaxToken>()";
}
else if (IsNodeList(f.Type))
{
return $"{GetParameterName(f)}.Node.ToGreenList<InternalSyntax.{GetElementType(f.Type)}>()";
}
else if (IsSeparatedNodeList(f.Type))
{
return $"{GetParameterName(f)}.Node.ToGreenSeparatedList<InternalSyntax.{GetElementType(f.Type)}>()";
}
else if (f.Type == "SyntaxNodeOrTokenList")
{
return $"{GetParameterName(f)}.Node.ToGreenList<GreenNode>()";
}
else if (f.Type == "DirectiveDescriptor")
{
return $"{GetParameterName(f)}";
}
else
{
return $"{GetParameterName(f)} == null ? null : (InternalSyntax.{f.Type}){GetParameterName(f)}.Green";
}
}),
// values are at end
valueFields.Select(f => GetParameterName(f)));
WriteLine(").CreateRed();");
if (hasValidation)
{
CloseBlock();
}
else
{
DescreaseIndent();
}
//WriteLine();
static bool NeedsNullCheck(Field field)
{
return !IsAnyList(field.Type) && !IsOptional(field);
}
static bool NeedsSyntaxKindValidation(Field field)
{
return field.Type == "SyntaxToken" && field.Kinds?.Count > 0;
}
}
private static string GetRedPropertyType(Field field)
{
return field.Type == "SyntaxList<SyntaxToken>"
? "SyntaxTokenList"
: field.Type;
}
private string GetDefaultValue(Node nd, Field field)
{
if (IsRequiredFactoryField(nd, field))
{
Console.WriteLine(nd.Name);
Console.WriteLine(field.Name);
}
System.Diagnostics.Debug.Assert(!IsRequiredFactoryField(nd, field));
if (IsOptional(field) || IsAnyList(field.Type))
{
return $"default({GetRedPropertyType(field)})";
}
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 node)
{
foreach (var field in node.Fields)
{
if (field.Type == "SyntaxToken" && CanBeAutoCreated(node, field) && field.Kinds.Count > 1)
{
WriteLine();
WriteLine($"private static SyntaxKind Get{StripPost(node.Name, "Syntax")}{StripPost(field.Name, "Opt")}Kind(SyntaxKind kind)");
using (Indent())
{
WriteLine("=> kind switch");
using (Block())
{
for (var i = 0; i < field.Kinds.Count; i++)
{
var nKind = node.Kinds[i];
var pKind = field.Kinds[i];
WriteLine($"SyntaxKind.{nKind.Name} => SyntaxKind.{pKind.Name}");
}
WriteLine($"_ => ThrowHelper.ThrowArgumentOutOfRangeException<SyntaxKind>(nameof(kind));");
}
}
}
}
}
private static IEnumerable<Field> DetermineRedFactoryWithNoAutoCreatableTokenFields(Node node)
{
return node.Fields.Where(f => !IsAutoCreatableToken(node, f));
}
// creates a factory without auto-creatable token arguments
private void WriteRedFactoryWithNoAutoCreatableTokens(Node node)
{
var nAutoCreatableTokens = node.Fields.Count(f => IsAutoCreatableToken(node, f));
if (nAutoCreatableTokens == 0)
{
return; // already handled by general factory
}
var factoryWithNoAutoCreatableTokenFields = new HashSet<Field>(DetermineRedFactoryWithNoAutoCreatableTokenFields(node));
var minimalFactoryFields = DetermineMinimalFactoryFields(node);
if (minimalFactoryFields != null && factoryWithNoAutoCreatableTokenFields.SetEquals(minimalFactoryFields))
{
return; // will be handled in minimal factory case
}
WriteLine();
WriteComment($"<summary>Creates a new {node.Name} instance.</summary>");
Write($"public static {node.Name} {StripPost(node.Name, "Syntax")}(");
WriteCommaSeparatedList(
node.Kinds.Count > 1 ? "SyntaxKind kind" : string.Empty,
node.Fields
.Where(factoryWithNoAutoCreatableTokenFields.Contains)
.Select(f => $"{GetRedPropertyType(f)} {GetParameterName(f)}"));
WriteLine(")");
using (Indent())
{
Write($"=> SyntaxFactory.{StripPost(node.Name, "Syntax")}(");
WriteCommaSeparatedList(
node.Kinds.Count > 1 ? "kind" : string.Empty,
node.Fields.Select(f => factoryWithNoAutoCreatableTokenFields.Contains(f)
? GetParameterName(f)
: GetDefaultValue(node, 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
var listCount = nd.Fields.Count(f => IsAnyNodeList(f.Type));
if (listCount == 1)
{
return nd.Fields.First(f => IsAnyNodeList(f.Type));
}
else
{
// otherwise, if there is a single optional node, use that..
var 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 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 node, bool withStringNames = false)
{
var optionalCount = OptionalFactoryArgumentCount(node);
if (optionalCount == 0)
{
return; // already handled w/ general factory method
}
var minimalFactoryfields = new HashSet<Field>(DetermineMinimalFactoryFields(node));
if (withStringNames && !minimalFactoryfields.Any(f => IsRequiredFactoryField(node, f) && CanAutoConvertFromString(f)))
{
return; // no string-name overload necessary
}
WriteLine();
WriteComment($"<summary>Creates a new {node.Name} instance.</summary>");
Write($"public static {node.Name} {StripPost(node.Name, "Syntax")}(");
WriteCommaSeparatedList(
node.Kinds.Count > 1 ? "SyntaxKind kind" : string.Empty,
node.Fields.Where(minimalFactoryfields.Contains).Select(f =>
{
var type = GetRedPropertyType(f);
if (IsRequiredFactoryField(node, f))
{
if (withStringNames && CanAutoConvertFromString(f))
{
type = "string";
}
return $"{type} {GetParameterName(f)}";
}
else
{
return $"{type} {GetParameterName(f)} = default({type})";
}
}));
WriteLine(")");
using (Indent())
{
Write($"=> SyntaxFactory.{StripPost(node.Name, "Syntax")}(");
WriteCommaSeparatedList(
node.Kinds.Count > 1 ? "kind" : string.Empty,
node.Fields.Select(f =>
{
if (minimalFactoryfields.Contains(f))
{
if (IsRequiredFactoryField(node, f))
{
if (withStringNames && CanAutoConvertFromString(f))
{
return $"{GetStringConverterMethod(f)}({GetParameterName(f)})";
}
else
{
return GetParameterName(f);
}
}
else
{
if (IsOptional(f) || IsAnyList(f.Type))
{
return GetParameterName(f);
}
else
{
return $"{GetParameterName(f)} ?? {GetDefaultValue(node, f)}";
}
}
}
return GetDefaultValue(node, f);
}));
WriteLine(");");
}
}
private static bool CanAutoConvertFromString(Field field)
{
return IsIdentifierToken(field) || IsIdentifierNameSyntax(field);
}
private static bool IsIdentifierToken(Field field)
{
return field.Type == "SyntaxToken" && field.Kinds != null && field.Kinds.Count == 1 && field.Kinds[0].Name == "IdentifierToken";
}
private static bool IsIdentifierNameSyntax(Field field)
{
return field.Type == "IdentifierNameSyntax";
}
private static 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("/// {0}", 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)
{
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("/// {0}", line.TrimStart());
}
}
}
}
private static string GetFieldName(Field field)
=> UnderscoreCamelCase(field.Name);
private static string GetParameterName(Field field)
=> CamelCase(field.Name);
}
|