From aa4c9134ab9e1668c2aa8f44ab9948652874f3f1 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Thu, 7 Mar 2019 17:03:09 -0800 Subject: [PATCH] Added RazorSyntaxGenerator to the repo --- .../AbstractFileWriter.cs | 358 +++ .../tools/RazorSyntaxGenerator/Program.cs | 112 + .../tools/RazorSyntaxGenerator/README.md | 11 + .../RazorSyntaxGenerator.csproj | 12 + .../RazorSyntaxGenerator/SignatureWriter.cs | 130 + .../RazorSyntaxGenerator/SourceWriter.cs | 2259 +++++++++++++++++ 6 files changed, 2882 insertions(+) create mode 100644 src/Razor/tools/RazorSyntaxGenerator/AbstractFileWriter.cs create mode 100644 src/Razor/tools/RazorSyntaxGenerator/Program.cs create mode 100644 src/Razor/tools/RazorSyntaxGenerator/README.md create mode 100644 src/Razor/tools/RazorSyntaxGenerator/RazorSyntaxGenerator.csproj create mode 100644 src/Razor/tools/RazorSyntaxGenerator/SignatureWriter.cs create mode 100644 src/Razor/tools/RazorSyntaxGenerator/SourceWriter.cs diff --git a/src/Razor/tools/RazorSyntaxGenerator/AbstractFileWriter.cs b/src/Razor/tools/RazorSyntaxGenerator/AbstractFileWriter.cs new file mode 100644 index 00000000000..bae61b5e74a --- /dev/null +++ b/src/Razor/tools/RazorSyntaxGenerator/AbstractFileWriter.cs @@ -0,0 +1,358 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace RazorSyntaxGenerator +{ + internal abstract class AbstractFileWriter + { + private readonly TextWriter _writer; + private readonly Tree _tree; + private readonly IDictionary _parentMap; + private readonly ILookup _childMap; + private readonly IDictionary _nodeMap; + private readonly IDictionary _typeMap; + + private const int INDENT_SIZE = 4; + private int _indentLevel; + private bool _needIndent = true; + + protected AbstractFileWriter(TextWriter writer, Tree tree) + { + _writer = writer; + _tree = tree; + _nodeMap = tree.Types.OfType().ToDictionary(n => n.Name); + _typeMap = tree.Types.ToDictionary(n => n.Name); + _parentMap = tree.Types.ToDictionary(n => n.Name, n => n.Base); + _parentMap.Add(tree.Root, null); + _childMap = tree.Types.ToLookup(n => n.Base, n => n.Name); + } + + protected IDictionary ParentMap { get { return _parentMap; } } + protected ILookup ChildMap { get { return _childMap; } } + protected Tree Tree { get { return _tree; } } + + #region Output helpers + + protected void Indent() + { + _indentLevel++; + } + + protected void Unindent() + { + if (_indentLevel <= 0) + { + throw new InvalidOperationException("Cannot unindent from base level"); + } + _indentLevel--; + } + + protected void Write(string msg) + { + WriteIndentIfNeeded(); + _writer.Write(msg); + } + + protected void Write(string msg, params object[] args) + { + WriteIndentIfNeeded(); + _writer.Write(msg, args); + } + + protected void WriteLine() + { + WriteLine(""); + } + + protected void WriteLine(string msg) + { + WriteIndentIfNeeded(); + _writer.WriteLine(msg); + _needIndent = true; //need an indent after each line break + } + + protected void WriteLine(string msg, params object[] args) + { + WriteIndentIfNeeded(); + _writer.WriteLine(msg, args); + _needIndent = true; //need an indent after each line break + } + + private void WriteIndentIfNeeded() + { + if (_needIndent) + { + _writer.Write(new string(' ', _indentLevel * INDENT_SIZE)); + _needIndent = false; + } + } + + protected void OpenBlock() + { + WriteLine("{"); + Indent(); + } + + protected void CloseBlock() + { + Unindent(); + WriteLine("}"); + } + + #endregion Output helpers + + #region Node helpers + + protected static string OverrideOrNewModifier(Field field) + { + return IsOverride(field) ? "override " : IsNew(field) ? "new " : ""; + } + + protected static bool CanBeField(Field field) + { + return field.Type != "SyntaxToken" && !IsAnyList(field.Type) && !IsOverride(field) && !IsNew(field); + } + + protected static string GetFieldType(Field field, bool green) + { + if (IsAnyList(field.Type)) + { + return green + ? "GreenNode" + : "SyntaxNode"; + } + + return field.Type; + } + + protected bool IsDerivedOrListOfDerived(string baseType, string derivedType) + { + return IsDerivedType(baseType, derivedType) + || ((IsNodeList(derivedType) || IsSeparatedNodeList(derivedType)) + && IsDerivedType(baseType, GetElementType(derivedType))); + } + + protected static bool IsSeparatedNodeList(string typeName) + { + return typeName.StartsWith("SeparatedSyntaxList<", StringComparison.Ordinal); + } + + protected static bool IsNodeList(string typeName) + { + return typeName.StartsWith("SyntaxList<", StringComparison.Ordinal); + } + + protected static bool IsAnyNodeList(string typeName) + { + return IsNodeList(typeName) || IsSeparatedNodeList(typeName); + } + + protected bool IsNodeOrNodeList(string typeName) + { + return IsNode(typeName) || IsNodeList(typeName) || IsSeparatedNodeList(typeName) || typeName == "SyntaxNodeOrTokenList"; + } + + protected static string GetElementType(string typeName) + { + if (!typeName.Contains("<")) + return string.Empty; + var iStart = typeName.IndexOf('<'); + var iEnd = typeName.IndexOf('>', iStart + 1); + if (iEnd < iStart) + return string.Empty; + var sub = typeName.Substring(iStart + 1, iEnd - iStart - 1); + return sub; + } + + protected static bool IsAnyList(string typeName) + { + return IsNodeList(typeName) || IsSeparatedNodeList(typeName) || typeName == "SyntaxNodeOrTokenList"; + } + + protected bool IsDerivedType(string typeName, string derivedTypeName) + { + if (typeName == derivedTypeName) + return true; + if (derivedTypeName != null && _parentMap.TryGetValue(derivedTypeName, out var baseType)) + { + return IsDerivedType(typeName, baseType); + } + return false; + } + + protected static bool IsRoot(Node n) + { + return n.Root != null && string.Compare(n.Root, "true", true) == 0; + } + + protected bool IsNode(string typeName) + { + return _parentMap.ContainsKey(typeName); + } + + protected Node GetNode(string typeName) + => _nodeMap.TryGetValue(typeName, out var node) ? node : null; + + protected TreeType GetTreeType(string typeName) + => _typeMap.TryGetValue(typeName, out var node) ? node : null; + + protected static bool IsOptional(Field f) + { + return f.Optional != null && string.Compare(f.Optional, "true", true) == 0; + } + + protected static bool IsOverride(Field f) + { + return f.Override != null && string.Compare(f.Override, "true", true) == 0; + } + + protected static bool IsNew(Field f) + { + return f.New != null && string.Compare(f.New, "true", true) == 0; + } + + protected static bool HasErrors(Node n) + { + return n.Errors == null || string.Compare(n.Errors, "true", true) == 0; + } + + protected static string CamelCase(string name) + { + if (char.IsUpper(name[0])) + { + name = char.ToLowerInvariant(name[0]) + name.Substring(1); + } + return FixKeyword(name); + } + + protected static string FixKeyword(string name) + { + if (IsKeyword(name)) + { + return "@" + name; + } + return name; + } + + protected static string UnderscoreCamelCase(string name) + { + return "_" + CamelCase(name); + } + + protected string StripNode(string name) + { + return (_tree.Root.EndsWith("Node", StringComparison.Ordinal)) ? _tree.Root.Substring(0, _tree.Root.Length - 4) : _tree.Root; + } + + protected string StripRoot(string name) + { + var root = StripNode(_tree.Root); + if (name.EndsWith(root, StringComparison.Ordinal)) + { + return name.Substring(0, name.Length - root.Length); + } + return name; + } + + protected static string StripPost(string name, string post) + { + return name.EndsWith(post, StringComparison.Ordinal) + ? name.Substring(0, name.Length - post.Length) + : name; + } + + protected static bool IsKeyword(string name) + { + switch (name) + { + case "bool": + case "byte": + case "sbyte": + case "short": + case "ushort": + case "int": + case "uint": + case "long": + case "ulong": + case "double": + case "float": + case "decimal": + case "string": + case "char": + case "object": + case "typeof": + case "sizeof": + case "null": + case "true": + case "false": + case "if": + case "else": + case "while": + case "for": + case "foreach": + case "do": + case "switch": + case "case": + case "default": + case "lock": + case "try": + case "throw": + case "catch": + case "finally": + case "goto": + case "break": + case "continue": + case "return": + case "public": + case "private": + case "internal": + case "protected": + case "static": + case "readonly": + case "sealed": + case "const": + case "new": + case "override": + case "abstract": + case "virtual": + case "partial": + case "ref": + case "out": + case "in": + case "where": + case "params": + case "this": + case "base": + case "namespace": + case "using": + case "class": + case "struct": + case "interface": + case "delegate": + case "checked": + case "get": + case "set": + case "add": + case "remove": + case "operator": + case "implicit": + case "explicit": + case "fixed": + case "extern": + case "event": + case "enum": + case "unsafe": + return true; + default: + return false; + } + } + + #endregion Node helpers + } +} diff --git a/src/Razor/tools/RazorSyntaxGenerator/Program.cs b/src/Razor/tools/RazorSyntaxGenerator/Program.cs new file mode 100644 index 00000000000..63989d26e64 --- /dev/null +++ b/src/Razor/tools/RazorSyntaxGenerator/Program.cs @@ -0,0 +1,112 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Xml; +using System.Xml.Serialization; + +namespace RazorSyntaxGenerator +{ + public class Program + { + public static int Main(string[] args) + { + if (args.Length < 2 || args.Length > 2) + { + WriteUsage(); + return 1; + } + + var inputFile = args[0]; + + if (!File.Exists(inputFile)) + { + Console.WriteLine(Directory.GetCurrentDirectory()); + Console.WriteLine(inputFile + " not found."); + return 1; + } + + var writeSource = true; + var writeSignatures = false; + string outputFile = null; + + if (args.Length == 2) + { + if (args[1] == "/sig") + { + writeSignatures = true; + } + else + { + outputFile = args[1]; + } + } + + var reader = XmlReader.Create(inputFile, new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit }); + var serializer = new XmlSerializer(typeof(Tree)); + var tree = (Tree)serializer.Deserialize(reader); + + if (writeSignatures) + { + SignatureWriter.Write(Console.Out, tree); + } + else + { + if (writeSource) + { + var outputPath = outputFile.Trim('"'); + var prefix = Path.GetFileName(inputFile); + var outputMainFile = Path.Combine(outputPath, $"{prefix}.Main.Generated.cs"); + var outputInternalFile = Path.Combine(outputPath, $"{prefix}.Internal.Generated.cs"); + var outputSyntaxFile = Path.Combine(outputPath, $"{prefix}.Syntax.Generated.cs"); + + WriteToFile(tree, SourceWriter.WriteMain, outputMainFile); + WriteToFile(tree, SourceWriter.WriteInternal, outputInternalFile); + WriteToFile(tree, SourceWriter.WriteSyntax, outputSyntaxFile); + } + //if (writeTests) + //{ + // WriteToFile(tree, TestWriter.Write, outputFile); + //} + } + + return 0; + } + + private static void WriteUsage() + { + Console.WriteLine("Invalid usage"); + Console.WriteLine(typeof(Program).GetTypeInfo().Assembly.ManifestModule.Name + " input-file output-file [/write-test]"); + } + + private static void WriteToFile(Tree tree, Action writeAction, string outputFile) + { + var stringBuilder = new StringBuilder(); + var writer = new StringWriter(stringBuilder); + writeAction(writer, tree); + + var text = stringBuilder.ToString(); + int length; + do + { + length = text.Length; + text = text.Replace("{\r\n\r\n", "{\r\n"); + } while (text.Length != length); + + try + { + using (var outFile = new StreamWriter(File.Open(outputFile, FileMode.Create), Encoding.UTF8)) + { + outFile.Write(text); + } + } + catch (UnauthorizedAccessException) + { + Console.WriteLine("Unable to access {0}. Is it checked out?", outputFile); + } + } + } +} diff --git a/src/Razor/tools/RazorSyntaxGenerator/README.md b/src/Razor/tools/RazorSyntaxGenerator/README.md new file mode 100644 index 00000000000..0d3aee79121 --- /dev/null +++ b/src/Razor/tools/RazorSyntaxGenerator/README.md @@ -0,0 +1,11 @@ +# Razor syntax generator + +Syntax generator tool for the Razor syntax tree. This is a modified version of Roslyn's [CSharpSyntaxGenerator](https://github.com/dotnet/roslyn/tree/master/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator). For internal use only. + +## Usage + +dotnet run `path/to/Syntax.xml` `path/to/generated/output` + +E.g, + +`dotnet run ../Microsoft.AspNetCore.Razor.Language/Syntax/Syntax.xml ../Microsoft.AspNetCore.Razor.Language/Syntax/Generated/` \ No newline at end of file diff --git a/src/Razor/tools/RazorSyntaxGenerator/RazorSyntaxGenerator.csproj b/src/Razor/tools/RazorSyntaxGenerator/RazorSyntaxGenerator.csproj new file mode 100644 index 00000000000..93665f7d14d --- /dev/null +++ b/src/Razor/tools/RazorSyntaxGenerator/RazorSyntaxGenerator.csproj @@ -0,0 +1,12 @@ + + + + Generates Razor syntax nodes from xml. For internal use only. + netcoreapp2.1 + dotnet-razorsyntaxgenerator + RazorSyntaxGenerator + Exe + false + + + diff --git a/src/Razor/tools/RazorSyntaxGenerator/SignatureWriter.cs b/src/Razor/tools/RazorSyntaxGenerator/SignatureWriter.cs new file mode 100644 index 00000000000..e7aefb38b9b --- /dev/null +++ b/src/Razor/tools/RazorSyntaxGenerator/SignatureWriter.cs @@ -0,0 +1,130 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace RazorSyntaxGenerator +{ + internal class SignatureWriter + { + private readonly TextWriter _writer; + private readonly Tree _tree; + private readonly Dictionary _typeMap; + + private SignatureWriter(TextWriter writer, Tree tree) + { + _writer = writer; + _tree = tree; + _typeMap = tree.Types.ToDictionary(n => n.Name, n => n.Base); + _typeMap.Add(tree.Root, null); + } + + public static void Write(TextWriter writer, Tree tree) + { + new SignatureWriter(writer, tree).WriteFile(); + } + + private void WriteFile() + { + _writer.WriteLine("using System;"); + _writer.WriteLine("using System.Collections;"); + _writer.WriteLine("using System.Collections.Generic;"); + _writer.WriteLine("using System.Linq;"); + _writer.WriteLine("using System.Threading;"); + _writer.WriteLine(); + _writer.WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax"); + _writer.WriteLine("{"); + + this.WriteTypes(); + + _writer.WriteLine("}"); + } + + private void WriteTypes() + { + var nodes = _tree.Types.Where(n => !(n is PredefinedNode)).ToList(); + for (int i = 0, n = nodes.Count; i < n; i++) + { + var node = nodes[i]; + _writer.WriteLine(); + WriteType(node); + } + } + + private void WriteType(TreeType node) + { + if (node is AbstractNode abstractNode) + { + _writer.WriteLine(" public abstract partial class {0} : {1}", node.Name, node.Base); + _writer.WriteLine(" {"); + for (int i = 0, n = abstractNode.Fields.Count; i < n; i++) + { + var field = abstractNode.Fields[i]; + if (IsNodeOrNodeList(field.Type)) + { + _writer.WriteLine(" public abstract {0}{1} {2} {{ get; }}", "", field.Type, field.Name); + } + } + _writer.WriteLine(" }"); + } + else if (node is Node nd) + { + _writer.WriteLine(" public partial class {0} : {1}", node.Name, node.Base); + _writer.WriteLine(" {"); + + WriteKinds(nd.Kinds); + + var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); + var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList(); + + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + _writer.WriteLine(" public {0}{1}{2} {3} {{ get; }}", "", "", field.Type, field.Name); + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + _writer.WriteLine(" public {0}{1}{2} {3} {{ get; }}", "", "", field.Type, field.Name); + } + + _writer.WriteLine(" }"); + } + } + + private void WriteKinds(List kinds) + { + if (kinds.Count > 1) + { + foreach (var kind in kinds) + { + _writer.WriteLine(" // {0}", kind.Name); + } + } + } + + private bool IsSeparatedNodeList(string typeName) + { + return typeName.StartsWith("SeparatedSyntaxList<", StringComparison.Ordinal); + } + + private bool IsNodeList(string typeName) + { + return typeName.StartsWith("SyntaxList<", StringComparison.Ordinal); + } + + public bool IsNodeOrNodeList(string typeName) + { + return IsNode(typeName) || IsNodeList(typeName) || IsSeparatedNodeList(typeName); + } + + private bool IsNode(string typeName) + { + return _typeMap.ContainsKey(typeName); + } + } +} diff --git a/src/Razor/tools/RazorSyntaxGenerator/SourceWriter.cs b/src/Razor/tools/RazorSyntaxGenerator/SourceWriter.cs new file mode 100644 index 00000000000..71081f14fe1 --- /dev/null +++ b/src/Razor/tools/RazorSyntaxGenerator/SourceWriter.cs @@ -0,0 +1,2259 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +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("// "); + WriteLine(); + WriteLine("using System;"); + WriteLine("using System.Collections;"); + WriteLine("using System.Collections.Generic;"); + WriteLine("using System.Linq;"); + WriteLine("using System.Threading;"); + WriteLine(); + } + + private void WriteInternal() + { + WriteFileHeader(); + + WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax"); + WriteLine("{"); + WriteLine(); + WriteGreenTypes(); + WriteGreenVisitors(); + WriteGreenRewriter(); + WriteStaticGreenFactories(); + WriteLine("}"); + } + + private void WriteSyntax() + { + WriteFileHeader(); + + WriteLine(); + WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax"); + WriteLine("{"); + WriteLine(); + WriteRedTypes(); + WriteLine("}"); + } + + private void WriteMain() + { + WriteFileHeader(); + + WriteLine(); + WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax"); + WriteLine("{"); + //WriteLine(" using Microsoft.AspNetCore.Razor.Language.Syntax;"); + //WriteLine(); + WriteRedVisitors(); + WriteRedRewriter(); + WriteRedFactories(); + WriteLine("}"); + } + + private void WriteGreenTypes() + { + 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(); + WriteGreenType(node); + } + } + + private void WriteGreenType(TreeType node) + { + WriteComment(node.TypeComment, " "); + + if (node is AbstractNode) + { + AbstractNode nd = (AbstractNode)node; + WriteLine(" internal abstract partial class {0} : {1}", node.Name, node.Base == "SyntaxNode" ? "GreenNode" : node.Base); + WriteLine(" {"); + + // ctor with diagnostics and annotations + WriteLine(" internal {0}(SyntaxKind kind, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations)", node.Name); + WriteLine(" : base(kind, diagnostics, annotations)"); + WriteLine(" {"); + if (node.Name == "DirectiveTriviaSyntax") + { + WriteLine(" _flags |= NodeFlags.ContainsDirectives;"); + } + WriteLine(" }"); + + // ctor without diagnostics and annotations + WriteLine(" internal {0}(SyntaxKind kind)", node.Name); + WriteLine(" : base(kind)"); + WriteLine(" {"); + if (node.Name == "DirectiveTriviaSyntax") + { + WriteLine(" _flags |= NodeFlags.ContainsDirectives;"); + } + WriteLine(" }"); + + /* 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 = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); + var nodeFields = nd.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 {0}{1} {2} {{ get; }}", + (IsNew(field) ? "new " : ""), field.Type, field.Name); + } + else + { + WriteLine(" public abstract {0}{1} {2} {{ get; }}", + (IsNew(field) ? "new " : ""), field.Type, field.Name); + } + } + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + WriteLine(); + WriteComment(field.PropertyComment, " "); + + WriteLine(" public abstract {0}{1} {2} {{ get; }}", + (IsNew(field) ? "new " : ""), field.Type, field.Name); + } + + WriteLine(" }"); + } + else if (node is Node) + { + Node nd = (Node)node; + + WriteLine(" internal sealed partial class {0} : {1}", node.Name, node.Base); + WriteLine(" {"); + + var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); + var nodeFields = nd.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(" private readonly {0} {1};", type, UnderscoreCamelCase(field.Name)); + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + WriteLine(" private readonly {0} {1};", field.Type, UnderscoreCamelCase(field.Name)); + } + + // write constructor with diagnostics and annotations + WriteLine(); + Write(" internal {0}(SyntaxKind kind", node.Name); + + WriteGreenNodeConstructorArgs(nodeFields, valueFields); + + WriteLine(", RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations)"); + WriteLine(" : base(kind, diagnostics, annotations)"); + WriteLine(" {"); + WriteCtorBody(valueFields, nodeFields); + WriteLine(" }"); + WriteLine(); + + /* 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 and annotations + WriteLine(); + Write(" internal {0}(SyntaxKind kind", node.Name); + + WriteGreenNodeConstructorArgs(nodeFields, valueFields); + + WriteLine(")"); + WriteLine(" : base(kind)"); + WriteLine(" {"); + WriteCtorBody(valueFields, nodeFields); + WriteLine(" }"); + WriteLine(); + + // property accessors + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + WriteComment(field.PropertyComment, " "); + if (IsNodeList(field.Type)) + { + WriteLine(" public {0}{1} {2} {{ get {{ return new {1}({3}); }} }}", + OverrideOrNewModifier(field), field.Type, field.Name, UnderscoreCamelCase(field.Name) + ); + } + else if (IsSeparatedNodeList(field.Type)) + { + WriteLine(" public {0}{1} {2} {{ get {{ return new {1}(new SyntaxList({3})); }} }}", + OverrideOrNewModifier(field), field.Type, field.Name, UnderscoreCamelCase(field.Name), i + ); + } + else if (field.Type == "SyntaxNodeOrTokenList") + { + WriteLine(" public {0}SyntaxList {1} {{ get {{ return new SyntaxList({2}); }} }}", + OverrideOrNewModifier(field), field.Name, UnderscoreCamelCase(field.Name) + ); + } + else + { + WriteLine(" public {0}{1} {2} {{ get {{ return {3}; }} }}", + OverrideOrNewModifier(field), field.Type, field.Name, UnderscoreCamelCase(field.Name) + ); + } + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + WriteComment(field.PropertyComment, " "); + WriteLine(" public {0}{1} {2} {{ get {{ return {3}; }} }}", + OverrideOrNewModifier(field), field.Type, field.Name, UnderscoreCamelCase(field.Name) + ); + } + + // GetSlot + WriteLine(); + WriteLine(" internal override GreenNode GetSlot(int index)"); + WriteLine(" {"); + WriteLine(" switch (index)"); + WriteLine(" {"); + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + WriteLine(" case {0}: return {1};", i, UnderscoreCamelCase(field.Name)); + } + WriteLine(" default: return null;"); + WriteLine(" }"); + WriteLine(" }"); + + WriteLine(); + WriteLine(" internal override SyntaxNode CreateRed(SyntaxNode parent, int position)"); + WriteLine(" {"); + WriteLine(" return new Syntax.{0}(this, parent, position);", node.Name); + WriteLine(" }"); + + WriteGreenAcceptMethods(nd); + WriteGreenUpdateMethod(nd); + WriteSetDiagnostics(nd); + WriteSetAnnotations(nd); + + WriteLine(" }"); + } + } + + private void WriteGreenNodeConstructorArgs(List nodeFields, List valueFields) + { + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + string type = GetFieldType(field, green: true); + + Write(", {0} {1}", type, CamelCase(field.Name)); + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + Write(", {0} {1}", field.Type, CamelCase(field.Name)); + } + } + + private void WriteGreenSerialization(Node node) + { + var valueFields = node.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); + var nodeFields = node.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList(); + + // object reader constructor + WriteLine(); + WriteLine(" internal {0}(ObjectReader reader)", node.Name); + WriteLine(" : base(reader)"); + WriteLine(" {"); + + WriteLine(" this.SlotCount = {0};", nodeFields.Count); + + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + string type = GetFieldType(field, green: true); + WriteLine(" var {0} = ({1})reader.ReadValue();", CamelCase(field.Name), type); + WriteLine(" if ({0} != null)", CamelCase(field.Name)); + WriteLine(" {"); + WriteLine(" AdjustFlagsAndWidth({0});", CamelCase(field.Name)); + WriteLine(" this.{0} = {0};", CamelCase(field.Name), type); + WriteLine(" }"); + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + string type = GetFieldType(field, green: true); + WriteLine(" this.{0} = ({1})reader.{2}();", CamelCase(field.Name), type, GetReaderMethod(type)); + } + + WriteLine(" }"); + + // IWritable + WriteLine(); + WriteLine(" internal override void WriteTo(ObjectWriter writer)"); + WriteLine(" {"); + WriteLine(" base.WriteTo(writer);"); + + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + string type = GetFieldType(field, green: true); + WriteLine(" writer.WriteValue(this.{0});", CamelCase(field.Name)); + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + var type = GetFieldType(field, green: true); + WriteLine(" writer.{0}(this.{1});", GetWriterMethod(type), CamelCase(field.Name)); + } + + WriteLine(" }"); + + // IReadable + WriteLine(); + WriteLine(" static {0}()", node.Name); + WriteLine(" {"); + WriteLine(" ObjectBinder.RegisterTypeReader(typeof({0}), r => new {0}(r));", node.Name); + WriteLine(" }"); + } + + private string GetWriterMethod(string type) + { + switch (type) + { + case "bool": + return "WriteBoolean"; + default: + throw new InvalidOperationException(string.Format("Type '{0}' not supported for object reader serialization.", type)); + } + } + + private string GetReaderMethod(string type) + { + switch (type) + { + case "bool": + return "ReadBoolean"; + default: + throw new InvalidOperationException(string.Format("Type '{0}' not supported for object reader serialization.", type)); + } + } + + private void WriteCtorBody(List valueFields, List nodeFields) + { + // constructor body + WriteLine(" SlotCount = {0};", nodeFields.Count); + + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + if (IsAnyList(field.Type) || IsOptional(field)) + { + WriteLine(" if ({0} != null)", CamelCase(field.Name)); + WriteLine(" {"); + WriteLine(" AdjustFlagsAndWidth({0});", CamelCase(field.Name)); + WriteLine(" {0} = {1};", UnderscoreCamelCase(field.Name), CamelCase(field.Name)); + WriteLine(" }"); + } + else + { + WriteLine(" AdjustFlagsAndWidth({0});", CamelCase(field.Name)); + WriteLine(" {0} = {1};", UnderscoreCamelCase(field.Name), CamelCase(field.Name)); + } + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + WriteLine(" {0} = {1};", UnderscoreCamelCase(field.Name), CamelCase(field.Name)); + } + } + + private void WriteSetAnnotations(Node node) + { + WriteLine(); + WriteLine(" internal override GreenNode SetAnnotations(SyntaxAnnotation[] annotations)"); + WriteLine(" {"); + + Write(" return new {0}(", node.Name); + Write("Kind, "); + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (f > 0) + Write(", "); + Write("{0}", UnderscoreCamelCase(field.Name)); + } + WriteLine(", GetDiagnostics(), annotations);"); + WriteLine(" }"); + } + + private void WriteSetDiagnostics(Node node) + { + WriteLine(); + WriteLine(" internal override GreenNode SetDiagnostics(RazorDiagnostic[] diagnostics)"); + WriteLine(" {"); + + Write(" return new {0}(", node.Name); + Write("Kind, "); + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (f > 0) + Write(", "); + Write("{0}", UnderscoreCamelCase(field.Name)); + } + WriteLine(", diagnostics, GetAnnotations());"); + WriteLine(" }"); + } + + private void WriteGreenAcceptMethods(Node node) + { + //WriteLine(); + //WriteLine(" public override TResult Accept(SyntaxVisitor visitor, TArgument argument)"); + //WriteLine(" {"); + //WriteLine(" return visitor.Visit{0}(this, argument);", StripPost(node.Name, "Syntax")); + //WriteLine(" }"); + WriteLine(); + WriteLine(" public override TResult Accept(SyntaxVisitor visitor)"); + WriteLine(" {"); + WriteLine(" return visitor.Visit{0}(this);", StripPost(node.Name, "Syntax")); + WriteLine(" }"); + WriteLine(); + WriteLine(" public override void Accept(SyntaxVisitor visitor)"); + WriteLine(" {"); + WriteLine(" visitor.Visit{0}(this);", StripPost(node.Name, "Syntax")); + WriteLine(" }"); + } + + private void WriteGreenVisitors() + { + //WriteGreenVisitor(true, true); + //WriteLine(); + WriteGreenVisitor(false, true); + WriteLine(); + WriteGreenVisitor(false, false); + } + + private void WriteGreenVisitor(bool withArgument, bool withResult) + { + var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList(); + + WriteLine(); + WriteLine(" internal partial class SyntaxVisitor" + (withResult ? "<" + (withArgument ? "TArgument, " : "") + "TResult>" : "")); + WriteLine(" {"); + int nWritten = 0; + for (int i = 0, n = nodes.Count; i < n; i++) + { + if (nodes[i] is Node node) + { + if (nWritten > 0) + WriteLine(); + nWritten++; + WriteLine(" public virtual " + (withResult ? "TResult" : "void") + " Visit{0}({1} node{2})", StripPost(node.Name, "Syntax"), node.Name, withArgument ? ", TArgument argument" : ""); + WriteLine(" {"); + WriteLine(" " + (withResult ? "return " : "") + "DefaultVisit(node{0});", withArgument ? ", argument" : ""); + WriteLine(" }"); + } + } + WriteLine(" }"); + } + + private void WriteGreenUpdateMethod(Node node) + { + WriteLine(); + Write(" public {0} Update(", node.Name); + + // parameters + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (f > 0) + Write(", "); + + var type = + field.Type == "SyntaxNodeOrTokenList" ? "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList" : + field.Type == "SyntaxTokenList" ? "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList" : + IsNodeList(field.Type) ? "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax." + field.Type : + IsSeparatedNodeList(field.Type) ? "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax." + field.Type : + field.Type; + + Write("{0} {1}", type, CamelCase(field.Name)); + } + WriteLine(")"); + WriteLine(" {"); + + Write(" if ("); + int nCompared = 0; + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (IsDerivedOrListOfDerived("SyntaxNode", field.Type) || IsDerivedOrListOfDerived("SyntaxToken", field.Type) || field.Type == "SyntaxNodeOrTokenList") + { + if (nCompared > 0) + Write(" || "); + Write("{0} != {1}", CamelCase(field.Name), field.Name); + nCompared++; + } + } + if (nCompared > 0) + { + WriteLine(")"); + WriteLine(" {"); + Write(" var newNode = SyntaxFactory.{0}(", StripPost(node.Name, "Syntax")); + if (node.Kinds.Count > 1) + { + Write("Kind, "); + } + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (f > 0) + Write(", "); + Write(CamelCase(field.Name)); + } + WriteLine(");"); + WriteLine(" var diags = GetDiagnostics();"); + WriteLine(" if (diags != null && diags.Length > 0)"); + WriteLine(" newNode = newNode.WithDiagnosticsGreen(diags);"); + WriteLine(" var annotations = GetAnnotations();"); + WriteLine(" if (annotations != null && annotations.Length > 0)"); + WriteLine(" newNode = newNode.WithAnnotationsGreen(annotations);"); + WriteLine(" return newNode;"); + WriteLine(" }"); + } + + WriteLine(); + WriteLine(" return this;"); + WriteLine(" }"); + } + + private void WriteGreenRewriter() + { + var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList(); + + WriteLine(); + WriteLine(" internal partial class SyntaxRewriter : SyntaxVisitor"); + WriteLine(" {"); + int nWritten = 0; + for (int i = 0, n = nodes.Count; i < n; i++) + { + if (nodes[i] is Node node) + { + var nodeFields = node.Fields.Where(nd => IsNodeOrNodeList(nd.Type)).ToList(); + + if (nWritten > 0) + WriteLine(); + nWritten++; + WriteLine(" public override GreenNode Visit{0}({1} node)", StripPost(node.Name, "Syntax"), node.Name); + WriteLine(" {"); + for (int f = 0; f < nodeFields.Count; f++) + { + var field = nodeFields[f]; + if (IsAnyList(field.Type)) + { + WriteLine(" var {0} = VisitList(node.{1});", CamelCase(field.Name), field.Name); + } + else + { + WriteLine(" var {0} = ({1})Visit(node.{2});", CamelCase(field.Name), field.Type, field.Name); + } + } + if (nodeFields.Count > 0) + { + Write(" return node.Update("); + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (f > 0) + Write(", "); + if (IsNodeOrNodeList(field.Type)) + { + Write(CamelCase(field.Name)); + } + else + { + Write("node.{0}", field.Name); + } + } + WriteLine(");"); + } + else + { + WriteLine(" return node;"); + } + WriteLine(" }"); + } + } + WriteLine(" }"); + } + + private void WriteContextualGreenFactories() + { + var nodes = Tree.Types.Where(n => !(n is PredefinedNode) && !(n is AbstractNode)).ToList(); + WriteLine(); + WriteLine(" internal partial class ContextAwareSyntax"); + WriteLine(" {"); + + WriteLine(); + WriteLine(" private SyntaxFactoryContext context;"); + WriteLine(); + + WriteLine(); + WriteLine(" public ContextAwareSyntax(SyntaxFactoryContext context)"); + WriteLine(" {"); + WriteLine(" this.context = context;"); + WriteLine(" }"); + + WriteGreenFactories(nodes, withSyntaxFactoryContext: true); + + WriteLine(" }"); + } + + private void WriteStaticGreenFactories() + { + var nodes = Tree.Types.Where(n => !(n is PredefinedNode) && !(n is AbstractNode)).ToList(); + WriteLine(); + WriteLine(" internal static partial class SyntaxFactory"); + WriteLine(" {"); + + WriteGreenFactories(nodes); + + WriteGreenTypeList(); + + WriteLine(" }"); + } + + private void WriteGreenFactories(List 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 GetNodeTypes()"); + WriteLine(" {"); + WriteLine(" return new Type[] {"); + + var nodes = Tree.Types.Where(n => !(n is PredefinedNode) && !(n is AbstractNode)).ToList(); + for (int i = 0, n = nodes.Count; i < n; i++) + { + var node = nodes[i]; + Write(" typeof({0})", node.Name); + if (i < n - 1) + Write(","); + WriteLine(); + } + + WriteLine(" };"); + WriteLine(" }"); + } + + + 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 {0}{1} {2}(", withSyntaxFactoryContext ? "" : "static ", nd.Name, StripPost(nd.Name, "Syntax")); + WriteGreenFactoryParameters(nd); + WriteLine(")"); + WriteLine(" {"); + + // validate kind + if (nd.Kinds.Count > 1) + { + WriteLine(" switch (kind)"); + WriteLine(" {"); + foreach (var k in nd.Kinds) + { + WriteLine(" case SyntaxKind.{0}:", k.Name); + } + WriteLine(" break;"); + WriteLine(" default:"); + WriteLine(" throw new ArgumentException(\"kind\");"); + WriteLine(" }"); + } + + // validate parameters + //WriteLine("#if DEBUG"); + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + var pname = CamelCase(field.Name); + + if (!IsAnyList(field.Type) && !IsOptional(field)) + { + WriteLine(" if ({0} == null)", CamelCase(field.Name)); + WriteLine(" throw new ArgumentNullException(nameof({0}));", CamelCase(field.Name)); + } + if (field.Type == "SyntaxToken" && field.Kinds != null && field.Kinds.Count > 0) + { + if (IsOptional(field)) + { + WriteLine(" if ({0} != null)", CamelCase(field.Name)); + WriteLine(" {"); + } + WriteLine(" switch ({0}.Kind)", pname); + WriteLine(" {"); + foreach (var kind in field.Kinds) + { + WriteLine(" case SyntaxKind.{0}:", 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:"); + } + WriteLine(" break;"); + WriteLine(" default:"); + WriteLine(" throw new ArgumentException(\"{0}\");", pname); + WriteLine(" }"); + if (IsOptional(field)) + { + WriteLine(" }"); + } + } + } + + //WriteLine("#endif"); + + if (nd.Name != "SkippedTokensTriviaSyntax" && + nd.Name != "DocumentationCommentTriviaSyntax" && + nd.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 {0}(", nd.Name); + WriteCtorArgList(nd, 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}(", nd.Name); + WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields); + WriteLine(");"); + } + + 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"; + } + else if (IsSeparatedNodeList(field.Type) || + IsNodeList(field.Type)) + { + type = "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax." + type; + } + Write("{0} {1}", type, CamelCase(field.Name)); + } + } + + private void WriteCtorArgList(Node nd, bool withSyntaxFactoryContext, List valueFields, List 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" || IsAnyList(field.Type)) + { + Write("{0}.Node", CamelCase(field.Name)); + } + else + { + Write(CamelCase(field.Name)); + } + } + // values are at end + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + Write(", "); + Write(UnderscoreCamelCase(field.Name)); + } + 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 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(); + + private void WriteRedType(TreeType node) + { + WriteComment(node.TypeComment, " "); + + if (node is AbstractNode) + { + AbstractNode nd = (AbstractNode)node; + WriteLine(" internal abstract partial class {0} : {1}", node.Name, node.Base); + WriteLine(" {"); + WriteLine(" internal {0}(GreenNode green, SyntaxNode parent, int position)", node.Name); + WriteLine(" : base(green, parent, position)"); + WriteLine(" {"); + WriteLine(" }"); + + var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); + var nodeFields = GetNodeOrNodeListFields(nd); + + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + if (IsNodeOrNodeList(field.Type)) + { + //red SyntaxLists can't contain tokens, so we switch to SyntaxTokenList + var fieldType = GetRedFieldType(field); + WriteLine(); + WriteComment(field.PropertyComment, " "); + WriteLine(" {0} abstract {1}{2} {3} {{ get; }}", "public", (IsNew(field) ? "new " : ""), fieldType, field.Name); + 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 {0} Add{1}(params {2}[] items) => Add{1}Core(items);", node.Name, field.Name, argType); + WriteLine(" internal abstract {0} Add{1}Core(params {2}[] items);", node.Name, field.Name, argType); + } + else + { + var referencedNode = TryGetNodeForNestedList(field); + if (referencedNode != null) + { + for (int rf = 0; rf < referencedNode.Fields.Count; rf++) + { + var referencedNodeField = referencedNode.Fields[rf]; + if (IsAnyList(referencedNodeField.Type)) + { + var argType = GetElementType(referencedNodeField.Type); + + WriteLine(); + WriteLine(" public {0} Add{1}{2}(params {3}[] items) => Add{1}{2}Core(items);", node.Name, StripPost(field.Name, "Opt"), referencedNodeField.Name, argType); + WriteLine(" internal abstract {0} Add{1}{2}Core(params {3}[] items);", node.Name, StripPost(field.Name, "Opt"), referencedNodeField.Name, argType); + } + } + } + } + } + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + WriteLine(); + WriteComment(field.PropertyComment, " "); + WriteLine(" {0} abstract {1}{2} {3} {{ get; }}", "public", (IsNew(field) ? "new " : ""), field.Type, field.Name); + } + + 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 {0} Add{1}(params {2}[] items) => ({0})Add{1}Core(items);", node.Name, baseField.Name, argType); + } + else + { + var referencedNode = TryGetNodeForNestedList(baseField); + if (referencedNode != null) + { + // look for list members... + for (int rf = 0; rf < referencedNode.Fields.Count; rf++) + { + var referencedNodeField = referencedNode.Fields[rf]; + if (IsAnyList(referencedNodeField.Type)) + { + var argType = GetElementType(referencedNodeField.Type); + + WriteLine(); + WriteLine(" public new {0} Add{1}{2}(params {3}[] items) => Add{1}{2}Core(items);", baseType.Name, StripPost(baseField.Name, "Opt"), referencedNodeField.Name, argType); + } + } + } + } + } + } + + WriteLine(" }"); + } + else if (node is Node) + { + Node nd = (Node)node; + WriteLine(" internal sealed partial class {0} : {1}", node.Name, node.Base); + WriteLine(" {"); + + var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); + var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList(); + + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + //if (field.Type != "SyntaxToken" + // && field.Type != "SyntaxList" + // ) + //{ + if (IsSeparatedNodeList(field.Type) || field.Type == "SyntaxNodeOrTokenList") + { + WriteLine(" private SyntaxNode {0};", UnderscoreCamelCase(field.Name)); + } + else + { + var type = GetFieldType(field, green: false); + WriteLine(" private {0} {1};", type, UnderscoreCamelCase(field.Name)); + } + //} + } + + // write constructor + WriteLine(); + WriteLine(" internal {0}(GreenNode green, SyntaxNode parent, int position)", node.Name); + WriteLine(" : base(green, parent, position)"); + WriteLine(" {"); + WriteLine(" }"); + WriteLine(); + + // property accessors + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + //if (field.Type == "SyntaxToken") + //{ + // WriteComment(field.PropertyComment, " "); + // WriteLine(" {0} {1}{2} {3} ", "public", OverrideOrNewModifier(field), field.Type, field.Name); + // WriteLine(" {"); + // if (IsOptional(field)) + // { + // WriteLine(" get"); + // WriteLine(" {"); + // WriteLine(" var slot = ((InternalSyntax.{0})Green).{1};", node.Name, field.Name); + // WriteLine(" if (slot != null)"); + // WriteLine(" return new SyntaxToken(slot, this, {0});", GetChildPosition(i)/*, GetChildIndex(i)*/); + // WriteLine(); + // WriteLine(" return default(SyntaxToken);"); + // WriteLine(" }"); + // } + // else + // { + // WriteLine(" get {{ return new SyntaxToken(((InternalSyntax.{0})Green).{1}, this, {2}); }}", node.Name, field.Name, GetChildPosition(i)/*, GetChildIndex(i)*/); + // } + // WriteLine(" }"); + //} + /* Remove + else if (field.Type == "SyntaxList") + { + WriteComment(field.PropertyComment, " "); + WriteLine(" {0} {1}SyntaxTokenList {2} ", "public", OverrideOrNewModifier(field), field.Name); + WriteLine(" {"); + WriteLine(" get"); + WriteLine(" {"); + WriteLine(" var slot = Green.GetSlot({0});", i); + WriteLine(" if (slot != null)"); + WriteLine(" return new SyntaxTokenList(this, slot, {0}, {1});", GetChildPosition(i), GetChildIndex(i)); + WriteLine(); + WriteLine(" return default(SyntaxTokenList);"); + WriteLine(" }"); + WriteLine(" }"); + } */ + //else + //{ + WriteComment(field.PropertyComment, " "); + WriteLine(" {0} {1}{2} {3} ", "public", OverrideOrNewModifier(field), field.Type, field.Name); + WriteLine(" {"); + WriteLine(" get"); + WriteLine(" {"); + + if (IsNodeList(field.Type)) + { + WriteLine(" return new {0}(GetRed(ref {1}, {2}));", field.Type, UnderscoreCamelCase(field.Name), i); + } + else if (IsSeparatedNodeList(field.Type)) + { + WriteLine(" var red = GetRed(ref {0}, {1});", UnderscoreCamelCase(field.Name), i); + WriteLine(" if (red != null)", i); + WriteLine(" return new {0}(red, {1});", field.Type, GetChildIndex(i)); + WriteLine(); + WriteLine(" return default({0});", field.Type); + } + else if (field.Type == "SyntaxNodeOrTokenList") + { + throw new InvalidOperationException("field cannot be a random SyntaxNodeOrTokenList"); + } + else + { + if (i == 0) + { + WriteLine(" return GetRedAtZero(ref {0});", UnderscoreCamelCase(field.Name)); + } + else + { + WriteLine(" return GetRed(ref {0}, {1});", UnderscoreCamelCase(field.Name), i); + } + } + WriteLine(" }"); + WriteLine(" }"); + //} + WriteLine(); + } + + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + WriteComment(field.PropertyComment, " "); + WriteLine(" {0} {1}{2} {3} {{ get {{ return ((InternalSyntax.{4})Green).{3}; }} }}", + "public", OverrideOrNewModifier(field), field.Type, field.Name, node.Name + ); + WriteLine(); + } + + //GetNodeSlot forces creation of a red node. + WriteLine(" internal override SyntaxNode GetNodeSlot(int index)"); + WriteLine(" {"); + WriteLine(" switch (index)"); + WriteLine(" {"); + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + + //if (field.Type != "SyntaxToken" && field.Type != "SyntaxList") + if (true) + { + if (i == 0) + { + WriteLine(" case {0}: return GetRedAtZero(ref {1});", i, UnderscoreCamelCase(field.Name)); + } + else + { + WriteLine(" case {0}: return GetRed(ref {1}, {0});", i, UnderscoreCamelCase(field.Name)); + } + } + } + WriteLine(" default: return null;"); + WriteLine(" }"); + WriteLine(" }"); + + //GetCachedSlot returns a red node if we have it. + WriteLine(" internal override SyntaxNode GetCachedSlot(int index)"); + WriteLine(" {"); + WriteLine(" switch (index)"); + WriteLine(" {"); + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + //if (field.Type != "SyntaxToken" && field.Type != "SyntaxList") + if (true) + { + WriteLine(" case {0}: return {1};", i, UnderscoreCamelCase(field.Name)); + } + } + WriteLine(" default: return null;"); + WriteLine(" }"); + WriteLine(" }"); + + + WriteRedAcceptMethods(nd); + WriteRedUpdateMethod(nd); + WriteRedWithMethods(nd); + WriteRedListHelperMethods(nd); + + WriteLine(" }"); + } + } + + private static string GetRedFieldType(Field field) + { + //return field.Type == "SyntaxList" ? "SyntaxTokenList" : field.Type; + return field.Type; + } + + private string GetChildPosition(int i) + { + if (i == 0) + { + return "Position"; + } + else + { + return "GetChildPosition(" + i + ")"; + } + } + + private string GetChildIndex(int i) + { + if (i == 0) + { + return "0"; + } + else + { + return "GetChildIndex(" + i + ")"; + } + } + + private void WriteRedAcceptMethods(Node node) + { + //WriteRedAcceptMethod(node, true, true); + WriteRedAcceptMethod(node, false, true); + WriteRedAcceptMethod(node, false, false); + } + + private void WriteRedAcceptMethod(Node node, bool genericArgument, bool genericResult) + { + string genericArgs = + (genericResult && genericArgument) ? "" : + genericResult ? "" : ""; + WriteLine(); + WriteLine(" public override " + (genericResult ? "TResult" : "void") + " Accept" + genericArgs + "(SyntaxVisitor" + genericArgs + " visitor{0})", genericArgument ? ", TArgument argument" : ""); + WriteLine(" {"); + WriteLine(" " + (genericResult ? "return " : "") + "visitor.Visit{0}(this{1});", StripPost(node.Name, "Syntax"), genericArgument ? ", argument" : ""); + WriteLine(" }"); + } + + private void WriteRedVisitors() + { + //WriteRedVisitor(true, true); + WriteRedVisitor(false, true); + WriteRedVisitor(false, false); + } + + private void WriteRedVisitor(bool genericArgument, bool genericResult) + { + string genericArgs = + (genericResult && genericArgument) ? "" : + genericResult ? "" : ""; + var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList(); + + WriteLine(); + WriteLine(" internal partial class SyntaxVisitor" + genericArgs); + WriteLine(" {"); + int nWritten = 0; + for (int i = 0, n = nodes.Count; i < n; i++) + { + if (nodes[i] is Node node) + { + if (nWritten > 0) + WriteLine(); + nWritten++; + WriteComment(string.Format("Called when the visitor visits a {0} node.", node.Name), " "); + WriteLine(" public virtual " + (genericResult ? "TResult" : "void") + " Visit{0}({1} node{2})", StripPost(node.Name, "Syntax"), node.Name, genericArgument ? ", TArgument argument" : ""); + WriteLine(" {"); + WriteLine(" " + (genericResult ? "return " : "") + "DefaultVisit(node{0});", genericArgument ? ", argument" : ""); + WriteLine(" }"); + } + } + WriteLine(" }"); + } + + private void WriteRedUpdateMethod(Node node) + { + WriteLine(); + Write(" {0} {1} Update(", "public", node.Name); + + // parameters + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (f > 0) + Write(", "); + //var type = field.Type == "SyntaxList" ? "SyntaxTokenList" : field.Type; + var type = field.Type; + Write("{0} {1}", type, CamelCase(field.Name)); + } + WriteLine(")"); + WriteLine(" {"); + + Write(" if ("); + int nCompared = 0; + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (IsDerivedOrListOfDerived("SyntaxNode", field.Type) || IsDerivedOrListOfDerived("SyntaxToken", field.Type) || field.Type == "SyntaxNodeOrTokenList") + { + if (nCompared > 0) + Write(" || "); + Write("{0} != {1}", CamelCase(field.Name), field.Name); + nCompared++; + } + } + if (nCompared > 0) + { + WriteLine(")"); + WriteLine(" {"); + Write(" var newNode = SyntaxFactory.{0}(", StripPost(node.Name, "Syntax")); + if (node.Kinds.Count > 1) + { + Write("Kind(), "); + } + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (f > 0) + Write(", "); + Write(CamelCase(field.Name)); + } + WriteLine(");"); + WriteLine(" var annotations = GetAnnotations();"); + WriteLine(" if (annotations != null && annotations.Length > 0)"); + WriteLine(" return newNode.WithAnnotations(annotations);"); + WriteLine(" return newNode;"); + WriteLine(" }"); + } + + WriteLine(); + WriteLine(" return this;"); + WriteLine(" }"); + } + + private void WriteRedWithMethod(Node node) + { + WriteLine(); + Write(" public {0} With(", node.Name); + + // parameters + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + var type = GetRedPropertyType(field); + Write("Optional<{0}> {1} = default(Optional<{0}>)", type, UnderscoreCamelCase(field.Name)); + if (f < node.Fields.Count - 1) + Write(", "); + } + WriteLine(")"); + WriteLine(" {"); + + Write(" return Update("); + + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + var parameterName = UnderscoreCamelCase(field.Name); + WriteLine(); + Write(" {0}.HasValue ? {0}.Value : {1}", parameterName, field.Name); + if (f < node.Fields.Count - 1) + Write(","); + } + + WriteLine(); + WriteLine(" );"); + + WriteLine(" }"); + } + + private void WriteRedWithMethods(Node node) + { + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + var type = GetRedPropertyType(field); + + 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} {CamelCase(field.Name)}) => With{field.Name}({CamelCase(field.Name)});"); + isNew = true; + } + } + + WriteLine($" public{(isNew ? " new " : " ")}{node.Name} With{StripPost(field.Name, "Opt")}({type} {CamelCase(field.Name)})"); + WriteLine(" {"); + + // call update inside each setter + Write(" return Update("); + for (int f2 = 0; f2 < node.Fields.Count; f2++) + { + var field2 = node.Fields[f2]; + if (f2 > 0) + Write(", "); + + if (field2 == field) + { + Write("{0}", CamelCase(field2.Name)); + } + else + { + Write("{0}", field2.Name); + } + } + WriteLine(");"); + + 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) + { + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + + 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... + for (int rf = 0; rf < referencedNode.Fields.Count; rf++) + { + var referencedNodeField = referencedNode.Fields[rf]; + if (IsAnyList(referencedNodeField.Type)) + { + 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 = GetHighestBaseTypeWithField(node, field.Name); + if (baseType != null) + { + WriteLine(" internal override {0} Add{1}Core(params {2}[] items) => Add{1}(items);", baseType.Name, field.Name, argType); + isNew = true; + } + } + + WriteLine(); + WriteLine($" public{(isNew ? " new " : " ")}{node.Name} Add{field.Name}(params {argType}[] items)"); + WriteLine(" {"); + WriteLine(" return With{0}(this.{1}.AddRange(items));", StripPost(field.Name, "Opt"), field.Name); + WriteLine(" }"); + } + + 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 {0} Add{1}{2}Core(params {3}[] items) => Add{1}{2}(items);", baseType.Name, StripPost(field.Name, "Opt"), referencedNodeField.Name, argType); + isNew = true; + } + } + + // AddBaseListTypes + WriteLine(); + WriteLine($" public{(isNew ? " new " : " ")}{node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items)"); + WriteLine(" {"); + + if (IsOptional(field)) + { + var factoryName = StripPost(referencedNode.Name, "Syntax"); + var varName = StripPost(UnderscoreCamelCase(field.Name), "Opt"); + WriteLine(" var {0} = this.{1} ?? SyntaxFactory.{2}();", varName, field.Name, factoryName); + WriteLine(" return this.With{0}({1}.With{2}({1}.{3}.AddRange(items)));", StripPost(field.Name, "Opt"), varName, StripPost(referencedNodeField.Name, "Opt"), referencedNodeField.Name); + } + else + { + WriteLine(" return this.With{0}(this.{1}.With{2}(this.{1}.{3}.AddRange(items)));", StripPost(field.Name, "Opt"), field.Name, StripPost(referencedNodeField.Name, "Opt"), referencedNodeField.Name); + } + + WriteLine(" }"); + } + + private void WriteRedRewriter() + { + var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList(); + + WriteLine(); + WriteLine(" internal partial class SyntaxRewriter : SyntaxVisitor"); + WriteLine(" {"); + int nWritten = 0; + for (int i = 0, n = nodes.Count; i < n; i++) + { + if (nodes[i] is Node node) + { + var nodeFields = node.Fields.Where(nd => IsNodeOrNodeList(nd.Type)).ToList(); + + if (nWritten > 0) + WriteLine(); + nWritten++; + WriteLine(" public override SyntaxNode Visit{0}({1} node)", StripPost(node.Name, "Syntax"), node.Name); + WriteLine(" {"); + for (int f = 0; f < nodeFields.Count; f++) + { + var field = nodeFields[f]; + if (IsAnyList(field.Type)) + { + WriteLine(" var {0} = VisitList(node.{1});", CamelCase(field.Name), field.Name); + } + else if (field.Type == "SyntaxToken") + { + WriteLine(" var {0} = ({1})VisitToken(node.{2});", CamelCase(field.Name), field.Type, field.Name); + } + else + { + WriteLine(" var {0} = ({1})Visit(node.{2});", CamelCase(field.Name), field.Type, field.Name); + } + } + if (nodeFields.Count > 0) + { + Write(" return node.Update("); + for (int f = 0; f < node.Fields.Count; f++) + { + var field = node.Fields[f]; + if (f > 0) + Write(", "); + if (IsNodeOrNodeList(field.Type)) + { + Write(CamelCase(field.Name)); + } + else + { + Write("node.{0}", field.Name); + } + } + WriteLine(");"); + } + else + { + WriteLine(" return node;"); + } + WriteLine(" }"); + } + } + WriteLine(" }"); + } + + private void WriteRedFactories() + { + var nodes = Tree.Types.Where(n => !(n is PredefinedNode) && !(n is AbstractNode)).OfType().ToList(); + WriteLine(); + WriteLine(" internal static partial class SyntaxFactory"); + WriteLine(" {"); + + for (int i = 0, n = nodes.Count; i < n; i++) + { + var node = nodes[i]; + WriteRedFactory(node); + WriteRedFactoryWithNoAutoCreatableTokens(node); + WriteRedMinimalFactory(node); + WriteRedMinimalFactory(node, withStringNames: true); + WriteKindConverters(node); + } + + WriteLine(" }"); + } + + protected bool CanBeAutoCreated(Node node, Field field) + { + return IsAutoCreatableToken(node, field) || IsAutoCreatableNode(node, 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(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); + } + + private int RequiredFactoryArgumentCount(Node nd, bool includeKind = true) + { + int 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) + { + int 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 nd) + { + WriteLine(); + + var valueFields = nd.Fields.Where(n => IsValueField(n)).ToList(); + var nodeFields = nd.Fields.Where(n => !IsValueField(n)).ToList(); + + WriteComment(string.Format("Creates a new {0} instance.", nd.Name), " "); + + Write(" {0} static {1} {2}(", "public", nd.Name, StripPost(nd.Name, "Syntax")); + WriteRedFactoryParameters(nd); + + WriteLine(")"); + WriteLine(" {"); + + // validate kinds + if (nd.Kinds.Count > 1) + { + WriteLine(" switch (kind)"); + WriteLine(" {"); + foreach (var kind in nd.Kinds) + { + WriteLine(" case SyntaxKind.{0}:", kind.Name); + } + WriteLine(" break;"); + WriteLine(" default:"); + WriteLine(" throw new ArgumentException(\"kind\");"); + WriteLine(" }"); + } + + // validate parameters + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + var pname = CamelCase(field.Name); + + if (field.Type == "SyntaxToken") + { + if (field.Kinds != null && field.Kinds.Count > 0) + { + if (IsOptional(field)) + { + WriteLine(" if ({0} != null)", CamelCase(field.Name)); + WriteLine(" {"); + } + WriteLine(" switch ({0}.Kind)", pname); + WriteLine(" {"); + foreach (var kind in field.Kinds) + { + WriteLine(" case SyntaxKind.{0}:", kind.Name); + } + if (IsOptional(field)) + { + WriteLine(" case SyntaxKind.None:"); + } + WriteLine(" break;"); + WriteLine(" default:"); + WriteLine(" throw new ArgumentException(\"{0}\");", pname); + WriteLine(" }"); + if (IsOptional(field)) + { + WriteLine(" }"); + } + } + } + else if (!IsAnyList(field.Type) && !IsOptional(field)) + { + WriteLine(" if ({0} == null)", CamelCase(field.Name)); + WriteLine(" throw new ArgumentNullException(nameof({0}));", CamelCase(field.Name)); + } + } + + Write(" return ({0})InternalSyntax.SyntaxFactory.{1}(", nd.Name, StripPost(nd.Name, "Syntax")); + if (nd.Kinds.Count > 1) + { + Write("kind, "); + } + for (int i = 0, n = nodeFields.Count; i < n; i++) + { + var field = nodeFields[i]; + if (i > 0) + Write(", "); + if (field.Type == "SyntaxToken") + { + if (IsOptional(field)) + { + Write("(Syntax.InternalSyntax.SyntaxToken){0}?.Green", CamelCase(field.Name)); + } + else + { + Write("(Syntax.InternalSyntax.SyntaxToken){0}.Green", CamelCase(field.Name)); + } + } + else if (field.Type == "SyntaxList") + { + Write("{0}.Node.ToGreenList()", CamelCase(field.Name)); + } + else if (IsNodeList(field.Type)) + { + Write("{0}.Node.ToGreenList()", CamelCase(field.Name), GetElementType(field.Type)); + } + else if (IsSeparatedNodeList(field.Type)) + { + Write("{0}.Node.ToGreenSeparatedList()", CamelCase(field.Name), GetElementType(field.Type)); + } + else if (field.Type == "SyntaxNodeOrTokenList") + { + Write("{0}.Node.ToGreenList()", CamelCase(field.Name)); + } + else + { + Write("{0} == null ? null : (InternalSyntax.{1}){0}.Green", CamelCase(field.Name), field.Type); + } + } + + // values are at end + for (int i = 0, n = valueFields.Count; i < n; i++) + { + var field = valueFields[i]; + Write(", "); + Write(CamelCase(field.Name)); + } + + WriteLine(").CreateRed();"); + WriteLine(" }"); + + //WriteLine(); + } + + private void WriteRedFactoryParameters(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 = GetRedPropertyType(field); + + Write("{0} {1}", type, CamelCase(field.Name)); + } + } + + private string GetRedPropertyType(Field field) + { + //if (field.Type == "SyntaxList") + // return "SyntaxTokenList"; + return 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 string.Format("default({0})", GetRedPropertyType(field)); + } + else if (field.Type == "SyntaxToken") + { + // auto construct token? + if (field.Kinds.Count == 1) + { + return string.Format("SyntaxFactory.Token(SyntaxKind.{0})", field.Kinds[0].Name); + } + else + { + return string.Format("SyntaxFactory.Token(Get{0}{1}Kind(kind))", StripPost(nd.Name, "Syntax"), StripPost(field.Name, "Opt")); + } + } + else + { + var referencedNode = GetNode(field.Type); + return string.Format("SyntaxFactory.{0}()", StripPost(referencedNode.Name, "Syntax")); + } + } + + // Writes GetKind() methods for converting between node kind and member token kinds... + private void WriteKindConverters(Node nd) + { + for (int f = 0; f < nd.Fields.Count; f++) + { + var field = nd.Fields[f]; + + if (field.Type == "SyntaxToken" && CanBeAutoCreated(nd, field) && field.Kinds.Count > 1) + { + WriteLine(); + WriteLine(" private static SyntaxKind Get{0}{1}Kind(SyntaxKind kind)", StripPost(nd.Name, "Syntax"), StripPost(field.Name, "Opt")); + WriteLine(" {"); + + WriteLine(" switch (kind)"); + WriteLine(" {"); + + for (int k = 0; k < field.Kinds.Count; k++) + { + var nKind = nd.Kinds[k]; + var pKind = field.Kinds[k]; + WriteLine(" case SyntaxKind.{0}:", nKind.Name); + WriteLine(" return SyntaxKind.{0};", pKind.Name); + } + + WriteLine(" default:"); + WriteLine(" throw new ArgumentOutOfRangeException();"); + WriteLine(" }"); + WriteLine(" }"); + } + } + } + + private IEnumerable 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(DetermineRedFactoryWithNoAutoCreatableTokenFields(nd)); + var minimalFactoryFields = DetermineMinimalFactoryFields(nd); + if (minimalFactoryFields != null && factoryWithNoAutoCreatableTokenFields.SetEquals(minimalFactoryFields)) + { + return; // will be handled in minimal factory case + } + + WriteLine(); + + WriteComment(string.Format("Creates a new {0} instance.", nd.Name), " "); + Write(" {0} static {1} {2}(", "public", nd.Name, StripPost(nd.Name, "Syntax")); + + bool hasPreviousParameter = false; + if (nd.Kinds.Count > 1) + { + Write("SyntaxKind kind"); + hasPreviousParameter = true; + } + + for (int i = 0, n = nd.Fields.Count; i < n; i++) + { + var field = nd.Fields[i]; + + if (factoryWithNoAutoCreatableTokenFields.Contains(field)) + { + if (hasPreviousParameter) + Write(", "); + + Write("{0} {1}", GetRedPropertyType(field), CamelCase(field.Name)); + + hasPreviousParameter = true; + } + } + WriteLine(")"); + + WriteLine(" {"); + + Write(" return SyntaxFactory.{0}(", StripPost(nd.Name, "Syntax")); + + bool hasPreviousArgument = false; + if (nd.Kinds.Count > 1) + { + Write("kind"); + hasPreviousArgument = true; + } + + for (int i = 0, n = nd.Fields.Count; i < n; i++) + { + var field = nd.Fields[i]; + + if (hasPreviousArgument) + Write(", "); + + if (factoryWithNoAutoCreatableTokenFields.Contains(field)) + { + // pass supplied parameter on to general factory + Write("{0}", CamelCase(field.Name)); + } + else + { + // pass an auto-created token to the general factory + Write("{0}", GetDefaultValue(nd, field)); + } + + hasPreviousArgument = true; + } + + WriteLine(");"); + + 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)); + if (listCount == 1) + { + return nd.Fields.First(f => IsAnyNodeList(f.Type)); + } + 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 IEnumerable 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(DetermineMinimalFactoryFields(nd)); + + if (withStringNames && minimalFactoryfields.Count(f => IsRequiredFactoryField(nd, f) && CanAutoConvertFromString(f)) == 0) + return; // no string-name overload necessary + + WriteLine(); + + WriteComment(string.Format("Creates a new {0} instance.", nd.Name), " "); + Write(" {0} static {1} {2}(", "public", nd.Name, StripPost(nd.Name, "Syntax")); + + bool hasPreviousParameter = false; + if (nd.Kinds.Count > 1) + { + Write("SyntaxKind kind"); + hasPreviousParameter = true; + } + + for (int i = 0, n = nd.Fields.Count; i < n; i++) + { + var field = nd.Fields[i]; + + if (minimalFactoryfields.Contains(field)) + { + var type = GetRedPropertyType(field); + + if (IsRequiredFactoryField(nd, field)) + { + if (hasPreviousParameter) + Write(", "); + + if (withStringNames && CanAutoConvertFromString(field)) + { + type = "string"; + } + + Write("{0} {1}", type, CamelCase(field.Name)); + + hasPreviousParameter = true; + } + else + { + if (hasPreviousParameter) + Write(", "); + + Write("{0} {1} = default({0})", type, CamelCase(field.Name)); + + hasPreviousParameter = true; + } + } + } + WriteLine(")"); + + WriteLine(" {"); + + Write(" return SyntaxFactory.{0}(", StripPost(nd.Name, "Syntax")); + + bool hasPreviousArgument = false; + if (nd.Kinds.Count > 1) + { + Write("kind"); + hasPreviousArgument = true; + } + + for (int i = 0, n = nd.Fields.Count; i < n; i++) + { + var field = nd.Fields[i]; + + if (hasPreviousArgument) + Write(", "); + + if (minimalFactoryfields.Contains(field)) + { + if (IsRequiredFactoryField(nd, field)) + { + if (withStringNames && CanAutoConvertFromString(field)) + { + Write("{0}({1})", GetStringConverterMethod(field), CamelCase(field.Name)); + } + else + { + Write("{0}", CamelCase(field.Name)); + } + } + else + { + if (IsOptional(field) || IsAnyList(field.Type)) + { + Write("{0}", CamelCase(field.Name)); + } + else + { + Write("{0} ?? {1}", CamelCase(field.Name), GetDefaultValue(nd, field)); + } + } + } + else + { + var defaultValue = GetDefaultValue(nd, field); + Write(defaultValue); + } + + hasPreviousArgument = true; + } + + WriteLine(");"); + + WriteLine(" }"); + } + + 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(); + } + } + + /// + /// Anything inside a <Comment> tag gets written out (escaping untouched) as the + /// XML doc comment. Line breaks will be preserved. + /// + private void WriteComment(string comment, string indent) + { + 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}/// {1}", indent, line.TrimStart()); + } + } + } + + /// + /// Anything inside a <Comment> tag gets written out (escaping untouched) as the + /// XML doc comment. Line breaks will be preserved. + /// + 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("{0}/// {1}", indent, line.TrimStart()); + } + } + } + } + } +}