File: GreenNodes\GreenNodeFactoryWriter.vb
Web Access
Project: src\src\Tools\Source\CompilerGeneratorTools\Source\VisualBasicSyntaxGenerator\VisualBasicSyntaxGenerator.vbproj (VisualBasicSyntaxGenerator)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
 
'-----------------------------------------------------------------------------------------------------------
' This is the code that actually outputs the VB code that defines the node factories. It is passed a read and validated
' ParseTree, and outputs the code to for the node factories.
'-----------------------------------------------------------------------------------------------------------
 
Imports System.IO
 
' Class to write out the code for the code tree.
Friend Class GreenNodeFactoryWriter
    Inherits WriteUtils
 
    Private _writer As TextWriter    'output is sent here.
 
    ' Initialize the class with the parse tree to write.
    Public Sub New(parseTree As ParseTree)
        MyBase.New(parseTree)
    End Sub
 
    ' Write out the factory class to the given file.
    Public Sub WriteFactories(writer As TextWriter)
        _writer = writer
 
        GenerateFile()
    End Sub
 
    Private Sub GenerateFile()
        GenerateFactoryClass(contextual:=False)
        GenerateFactoryClass(contextual:=True)
    End Sub
 
    ' Generate the factory class
    Private Sub GenerateFactoryClass(contextual As Boolean)
        _writer.WriteLine()
        If Not String.IsNullOrEmpty(_parseTree.NamespaceName) Then
            _writer.WriteLine("Namespace {0}", Ident(_parseTree.NamespaceName) + ".Syntax.InternalSyntax")
            _writer.WriteLine()
        End If
 
        If contextual Then
            _writer.WriteLine("    Friend Class {0}", Ident(_parseTree.ContextualFactoryClassName))
            GenerateConstructor()
        Else
            _writer.WriteLine("    Friend Partial Class {0}", Ident(_parseTree.FactoryClassName))
        End If
 
        _writer.WriteLine()
        GenerateAllFactoryMethods(contextual)
 
        _writer.WriteLine("    End Class")
 
        If Not String.IsNullOrEmpty(_parseTree.NamespaceName) Then
            _writer.WriteLine("End Namespace")
        End If
    End Sub
 
    ' Generator all factory methods for all node structures.
    Private Sub GenerateAllFactoryMethods(contextual As Boolean)
        For Each nodeStructure In _parseTree.NodeStructures.Values
            If Not nodeStructure.NoFactory Then
                GenerateFactoryMethodsForStructure(nodeStructure, contextual)
            End If
        Next
    End Sub
 
    ' Generator all factory methods for a node structure.
    ' If a nodeStructure has 0 kinds, it is abstract and no factory method is generator
    ' If a nodeStructure has 1 kind, a factory method for that kind is generator
    ' If a nodestructure has >=2 kinds, a factory method for each kind is generated, plus one for the structure as a whole, unless name would conflict.
    Private Sub GenerateFactoryMethodsForStructure(nodeStructure As ParseNodeStructure, contextual As Boolean)
        If _parseTree.IsAbstract(nodeStructure) Then Return ' abstract structures don't have factory methods
 
        If nodeStructure.Name = "PunctuationSyntax" OrElse nodeStructure.Name = "KeywordSyntax" Then
            Return ' use SyntaxFactory.Token() instead, that one is manually implemented
        End If
 
        For Each nodeKind In nodeStructure.NodeKinds
            GenerateFactoryMethods(nodeStructure, nodeKind, contextual)
        Next
 
        ' Only generate one a structure-level factory method if >= 2 kinds, and the nodeStructure name doesn't conflict with a kind name.
        If nodeStructure.NodeKinds.Count >= 2 And Not _parseTree.NodeKinds.ContainsKey(FactoryName(nodeStructure)) Then
            GenerateFactoryMethods(nodeStructure, Nothing, contextual)
        End If
    End Sub
 
    ' Generate the factory method for a node structure, possibly customized to a particular kind.
    ' If kind is Nothing, generate a factory method that takes a Kind parameter, and can create any kind.
    ' If kind is not Nothing, generator a factory method customized to that particular kind.
    Private Sub GenerateFactoryMethods(nodeStructure As ParseNodeStructure, nodeKind As ParseNodeKind, contextual As Boolean)
        GenerateFactoryMethod(nodeStructure, nodeKind, internalForm:=True, contextual:=contextual)
 
        'Dim tokenChildren = AllFactoryChildrenOfStructure(nodeStructure).Where(
        '       Function(c)
        '           Return KindTypeStructure(c.ChildKind).IsToken
        '       End Function)
 
        'If nodeStructure.IsToken OrElse nodeStructure.IsTrivia OrElse tokenChildren.Any Then
        '    GenerateFactoryMethod(nodeStructure, nodeKind, True)
        'End If
 
    End Sub
 
    Private Sub CheckKind(structureName As String)
        _writer.WriteLine("            Debug.Assert(SyntaxFacts.Is{0}(kind))", structureName)
    End Sub
 
    Private Sub CheckParam(name As String)
        _writer.WriteLine("            Debug.Assert({0} IsNot Nothing)", name)
    End Sub
 
    Private Sub CheckStructureParam(parent As ParseNodeStructure, nodeKind As ParseNodeKind, child As ParseNodeChild, factoryFunctionName As String)
        Dim name = ChildParamName(child, factoryFunctionName)
        _writer.Write("            Debug.Assert({0} IsNot Nothing", name)
 
        Dim childNodeKind As ParseNodeKind = TryCast(child.ChildKind, ParseNodeKind)
 
        If childNodeKind IsNot Nothing Then
            _writer.WriteLine(" AndAlso {0}.Kind = SyntaxKind.{1})", name, childNodeKind.Name)
 
        ElseIf TypeOf child.ChildKind Is List(Of ParseNodeKind) Then
            If nodeKind IsNot Nothing Then
                childNodeKind = child.ChildKind(nodeKind.Name)
                If childNodeKind IsNot Nothing Then
                    _writer.WriteLine(" AndAlso {0}.Kind = SyntaxKind.{1})", name, childNodeKind.Name)
                Else
                    _writer.WriteLine(" AndAlso SyntaxFacts.Is{1}({0}.Kind))", name, FactoryName(parent) + child.Name)
                End If
            Else
                _writer.WriteLine(" AndAlso SyntaxFacts.Is{1}({0}.Kind))", name, FactoryName(parent) + child.Name)
            End If
        Else
            _writer.WriteLine(")")
        End If
    End Sub
 
    ' Generate the factory method for a node structure, possibly customized to a particular kind.
    ' If kind is Nothing, generate a factory method that takes a Kind parameter, and can create any kind.
    ' If kind is not Nothing, generator a factory method customized to that particular kind.
    ' The simplified form is:
    '   Defaults the text for any token with token-text defined
    '   Defaults the trivia to a single trailing space for any token
    Private Sub GenerateFactoryMethod(nodeStructure As ParseNodeStructure, nodeKind As ParseNodeKind, internalForm As Boolean, contextual As Boolean)
 
        Dim factoryFunctionName As String       ' name of the factory method.
        Dim allFields = GetAllFieldsOfStructure(nodeStructure)
        Dim allChildren = GetAllChildrenOfStructure(nodeStructure)
        Dim allFactoryChildren = GetAllFactoryChildrenOfStructure(nodeStructure)
        Dim tokenText As String = Nothing  ' If not nothing, the default text for a token.
 
        If nodeKind IsNot Nothing Then
            If nodeKind.NoFactory Then Return
 
            factoryFunctionName = FactoryName(nodeKind)
            If nodeStructure.IsToken AndAlso nodeKind.TokenText <> "" Then
                tokenText = nodeKind.TokenText
            End If
        Else
            If nodeStructure.NoFactory Then Return
 
            factoryFunctionName = FactoryName(nodeStructure)
        End If
 
        ' 1. Generate the Function line
        '------------------------------
        Dim needComma = False  ' do we need a comma before the next parameter?
 
        _writer.WriteLine()
        GenerateSummaryXmlComment(_writer, nodeStructure.Description)
 
        If nodeKind Is Nothing Then
 
            Dim kindsList = String.Join(", ", From kind In nodeStructure.NodeKinds Select kind.Name)
 
            GenerateParameterXmlComment(_writer, "kind", String.Format("A <see cref=""SyntaxKind""/> representing the specific kind of {0}. One of {1}.", nodeStructure.Name, kindsList))
        End If
 
        If nodeStructure.IsTerminal Then
            GenerateParameterXmlComment(_writer, "text", "The actual text of this token.")
        End If
 
        For Each child In allChildren
            GenerateParameterXmlComment(_writer, LowerFirstCharacter(OptionalChildName(child)), child.Description, escapeText:=True)
        Next
 
        _writer.Write(
                    "        {0} {1}{2}Function {3}(",
                    "Friend",
                    If(factoryFunctionName = "GetType" OrElse factoryFunctionName = "Equals",
                       "Shadows ",
                       ""
                    ),
                    If(contextual, "", "Shared "),
                    Ident(factoryFunctionName)
                )
 
        If nodeKind Is Nothing Then
            _writer.Write("kind As {0}", NodeKindType())
            needComma = True
        End If
 
        If nodeStructure.IsTerminal Then
            ' terminals have text, except in simplified form 
            If needComma Then _writer.Write(", ")
            _writer.Write("text as String")
            needComma = True
        End If
 
        ' Generate parameters for each field and child
        For Each field In allFields
            If needComma Then _writer.Write(", ")
            GenerateNodeStructureFieldParameter(field, factoryFunctionName)
            needComma = True
        Next
        For Each child In allFactoryChildren
            If needComma Then _writer.Write(", ")
            GenerateNodeStructureChildParameter(nodeStructure, child, factoryFunctionName)
            needComma = True
        Next
 
        If nodeStructure.IsToken Then
            ' tokens have trivia also.
            If needComma Then _writer.Write(", ") : needComma = False
            _writer.Write("leadingTrivia As GreenNode, trailingTrivia As GreenNode", StructureTypeName(_parseTree.RootStructure))
 
        End If
 
        _writer.WriteLine(") As {0}", StructureTypeName(nodeStructure))
 
        '2. Generate the contracts.
        '----------------------------------------
        If nodeStructure.IsTerminal Then
            ' terminals have text, except in simplified form 
            CheckParam("text")
        End If
 
        If nodeKind Is Nothing Then
            CheckKind(SyntaxFactName(nodeStructure))
        End If
 
        For Each child In allFactoryChildren
            If Not child.IsOptional Then
                ' No need to check lists they are value types.  It is OK for child.Node to be nothing
                If child.IsList Then
                    'CheckListParam(ChildParamName(child, factoryFunctionName), internalForm)
                Else
                    If KindTypeStructure(child.ChildKind).IsToken Then
                        CheckStructureParam(nodeStructure, nodeKind, child, factoryFunctionName)
                    Else
                        CheckParam(ChildParamName(child, factoryFunctionName))
                    End If
                End If
            End If
        Next
 
        '3. Generate the call to the constructor
        '----------------------------------------
 
        ' the non-simplified form calls the constructor
        If (nodeStructure.IsTerminal OrElse
            nodeStructure.Name = "SkippedTokensTriviaSyntax" OrElse
            nodeStructure.Name = "DocumentationCommentTriviaSyntax" OrElse
            nodeStructure.Name.EndsWith("DirectiveTriviaSyntax", StringComparison.Ordinal) OrElse
            nodeStructure.Name = "AttributeSyntax" OrElse
            allFields.Count + allChildren.Count > 3) Then
 
            _writer.Write("            Return New {0}(", StructureTypeName(nodeStructure))
            GenerateCtorArgs(nodeStructure, nodeKind, contextual, factoryFunctionName)
            _writer.WriteLine(")")
 
        Else
            _writer.WriteLine("")
            'Dim hash As Integer
            _writer.WriteLine("            Dim hash As Integer")
 
            'Dim cached = SyntaxNodeCache.TryGetNode(SyntaxKind.ReturnStatement, returnKeyword, expression, hash)
 
            If contextual Then
                _writer.Write("            Dim cached = VisualBasicSyntaxNodeCache.TryGetNode(")
            Else
                _writer.Write("            Dim cached = SyntaxNodeCache.TryGetNode(")
            End If
 
            GenerateCtorArgs(nodeStructure, nodeKind, contextual, factoryFunctionName)
            _writer.WriteLine(", hash)")
 
            'If cached IsNot Nothing Then
            _writer.WriteLine("            If cached IsNot Nothing Then")
            '    Return DirectCast(cached, ReturnStatementSyntax)
            _writer.WriteLine("                Return DirectCast(cached, {0})", StructureTypeName(nodeStructure))
            'End If
            _writer.WriteLine("            End If")
            _writer.WriteLine("")
 
            'Dim result = New ReturnStatementSyntax(SyntaxKind.ReturnStatement, returnKeyword, expression)
            _writer.Write("            Dim result = New {0}(", StructureTypeName(nodeStructure))
            GenerateCtorArgs(nodeStructure, nodeKind, contextual, factoryFunctionName)
            _writer.WriteLine(")")
            'If hash >= 0 Then
            _writer.WriteLine("            If hash >= 0 Then")
            '    SyntaxNodeCache.AddNode(result, hash)
            _writer.WriteLine("                SyntaxNodeCache.AddNode(result, hash)")
            'End If
            _writer.WriteLine("            End If")
            _writer.WriteLine("")
 
            'Return result
            _writer.WriteLine("            Return result")
 
        End If
 
        '4. Generate the End Function
        '----------------------------
        _writer.WriteLine("        End Function")
        _writer.WriteLine()
    End Sub
 
    Private Sub GenerateCtorArgs(nodeStructure As ParseNodeStructure,
                                 nodeKind As ParseNodeKind,
                                 contextual As Boolean,
                                 factoryFunctionName As String)
        Dim allFields = GetAllFieldsOfStructure(nodeStructure)
        Dim allChildren = GetAllChildrenOfStructure(nodeStructure)
 
        If nodeKind Is Nothing Then
            _writer.Write("kind")
        Else
            _writer.Write("{0}.{1}", NodeKindType(), Ident(nodeKind.Name))
        End If
 
        If nodeStructure.IsTerminal Then
            ' terminals have text
            _writer.Write(", text")
        End If
 
        If nodeStructure.IsToken Then
            ' tokens have trivia
            _writer.Write(", leadingTrivia, trailingTrivia")
        End If
 
        ' Generate parameters for each field and child
        For Each field In allFields
            _writer.Write(", {0}", FieldParamName(field, factoryFunctionName))
        Next
 
        For Each child In allChildren
            If child.NotInFactory Then
                _writer.Write(", Nothing")
            Else
                If child.IsList Then
                    _writer.Write(", {0}.Node", ChildParamName(child, factoryFunctionName))
                ElseIf Not True AndAlso KindTypeStructure(child.ChildKind).IsToken Then
                    _writer.Write(", {0}.Node)", ChildParamName(child, factoryFunctionName), ChildConstructorTypeRef(child, True))
                Else
                    _writer.Write(", {0}", ChildParamName(child, factoryFunctionName))
                End If
            End If
        Next
 
        If contextual Then
            _writer.Write(", _factoryContext")
        End If
    End Sub
 
    ' Generate a parameter corresponding to a node structure field
    Private Sub GenerateNodeStructureFieldParameter(field As ParseNodeField, Optional conflictName As String = Nothing)
        _writer.Write("{0} As {1}", FieldParamName(field, conflictName), FieldTypeRef(field))
    End Sub
 
    ' Generate a parameter corresponding to a node structure child
    Private Sub GenerateNodeStructureChildParameter(node As ParseNodeStructure, child As ParseNodeChild, Optional conflictName As String = Nothing)
        _writer.Write("{0} As {1}", ChildParamName(child, conflictName), ChildFactoryTypeRef(node, child, True, True))
    End Sub
 
    Private Sub GenerateConstructor()
        _writer.WriteLine()
        _writer.WriteLine("        Private ReadOnly _factoryContext As ISyntaxFactoryContext")
        _writer.WriteLine()
        _writer.WriteLine("        Sub New(factoryContext As ISyntaxFactoryContext)")
        _writer.WriteLine("            _factoryContext = factoryContext")
        _writer.WriteLine("        End Sub")
    End Sub
 
End Class