File: XML\ParseTreeDescription.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.
 
'-----------------------------------------------------------------------------------------------------------
' Defines the in-memory format of the parse tree description. Many of the structures also 
' contain some of the code for reading themselves from the input file.
'-----------------------------------------------------------------------------------------------------------
 
Imports System.Globalization
Imports System.Text
Imports System.Xml
Imports <xmlns="http://schemas.microsoft.com/VisualStudio/Roslyn/Compiler">
 
' Root class of the parse tree. All information about the parse tree is in
' stuff accessible from here. In particular, contains dictionaries of all 
' the various parts (structures, node kinds, enumerations, etc.)
Public Class ParseTree
    Public FileName As String           ' my file name
 
    Public NamespaceName As String      ' name of the namespace to put stuff in
 
    Public VisitorName As String        ' name of the visitor class
 
    Public RewriteVisitorName As String    ' name of the rewriting visitor class
 
    Public FactoryClassName As String   ' name of the factory class
 
    Public ContextualFactoryClassName As String   ' name of the contextual factory class
 
    ' Dictionary of all node-structure's, indexed by name
    Public NodeStructures As New Dictionary(Of String, ParseNodeStructure)
 
    ' Dictionary of all node-kinds, indexed by name
    Public NodeKinds As New Dictionary(Of String, ParseNodeKind)
 
    ' Dictionary of all enumerations, indexed by name
    Public Enumerations As New Dictionary(Of String, ParseEnumeration)
 
    ' Dictionary of all node-kind-alias's, indexed by name
    Public Aliases As New Dictionary(Of String, ParseNodeKindAlias)
 
    ' Determines which structures are abstract
    Public IsAbstract As New Dictionary(Of ParseNodeStructure, Boolean)
 
    ' Get the root structure.
    Public RootStructure As ParseNodeStructure
 
    ' Get the root structure for Tokens, Trivia
    Public RootToken, RootTrivia As ParseNodeStructure
 
    ' Remember nodes with errors so we only report one error per node.
    Private ReadOnly _elementsWithErrors As New Dictionary(Of XNode, Boolean)
 
    ' Report an error.
    Public Sub ReportError(referencingNode As XNode, message As String, ParamArray args As Object())
        Dim fullMessage As String = FileName
 
        If referencingNode IsNot Nothing Then
            If _elementsWithErrors.ContainsKey(referencingNode) Then
                ' We already reported an error on this node.
                Exit Sub
            End If
 
            _elementsWithErrors(referencingNode) = True      ' remember this so we only report errors on this node once.
            Dim lineInfo = CType(referencingNode, IXmlLineInfo)
            fullMessage += String.Format("({0})", lineInfo.LineNumber)
        End If
 
        fullMessage += String.Format(": " + message, args)
 
        Console.WriteLine(fullMessage)
    End Sub
 
    ' Does this struct have any children (including parents?
    Public Function HasAnyChildren(struct As ParseNodeStructure) As Boolean
        Return struct.Children.Count > 0 OrElse (struct.ParentStructure IsNot Nothing AndAlso HasAnyChildren(struct.ParentStructure))
    End Function
 
    ' We finished reading the tree. Do a bit of pre-processing.
    Public Sub FinishedReading()
        ' Go through each node-structure and check for the root. 
        RootStructure = Nothing
        For Each struct In NodeStructures.Values
            If struct.IsRoot Then
                If RootStructure IsNot Nothing Then
                    ReportError(RootStructure.Element, "More than one root node specified.")
                    ReportError(struct.Element, "More than one root node specified.")
                End If
                RootStructure = struct
            Else
                ' this node is derived from something else. Remember that.
                struct.ParentStructure.HasDerivedStructure = Not String.IsNullOrEmpty(struct.ParentStructureId)
            End If
 
            ' Check for token root.
            If struct.IsTokenRoot Then
                If RootToken IsNot Nothing Then ReportError(struct.Element, "More than one token root specified.")
                RootToken = struct
            End If
 
            ' Check for trivia root.
            If struct.IsTriviaRoot Then
                If RootTrivia IsNot Nothing Then ReportError(struct.Element, "More than one trivia root specified.")
                RootTrivia = struct
            End If
 
            IsAbstract(struct) = True
 
            ' Determine "tokens" and trivia by walking the hierarchy
            SetIsTokenAndIsTrivia(struct)
        Next
 
        ' Figure out abstract nodes - they have no kinds associated with them.
        For Each kind In NodeKinds.Values
            IsAbstract(kind.NodeStructure) = False
        Next
    End Sub
 
    ' Set the IsToken and IsTrivia flags on a struct
    Private Sub SetIsTokenAndIsTrivia(struct As ParseNodeStructure)
        ' Walk the hierarchy.
        Dim parent = struct
 
        While parent IsNot Nothing
            If parent.IsTokenRoot Then
                struct.IsToken = True
                Return
            End If
            If parent.IsTriviaRoot Then
                struct.IsTrivia = True
            End If
 
            parent = parent.ParentStructure
        End While
    End Sub
 
    Public Function ParseEnumType(enumString As String, referencingElement As XNode) As Object
        If (Enumerations.ContainsKey(enumString)) Then
            Return Enumerations(enumString)
        End If
 
        ReportError(referencingElement, "{0} is not a valid field type. You should add a node-kind entry in the syntax.xml.", enumString)
        Return Nothing
    End Function
 
    Public Function ParseOneNodeKind(typeString As String, referencingNode As XNode) As ParseNodeKind
        If (NodeKinds.ContainsKey(typeString)) Then
            Return NodeKinds(typeString)
        End If
 
        ReportError(referencingNode, "{0} is not a valid node kind", typeString)
        Return Nothing
    End Function
 
    Public Function ParseNodeKind(typeParts As IList(Of String), referencingNode As XNode) As Object
        Dim typeList As New List(Of ParseNodeKind)
 
        For Each typePart In typeParts
            Dim foundType = ParseNodeKind(typePart, referencingNode)
            If (TypeOf foundType Is List(Of ParseNodeKind)) Then
                typeList.AddRange(CType(foundType, List(Of ParseNodeKind)))
            Else
                If Not TypeOf foundType Is ParseNodeKind Then
                    ReportError(referencingNode, "{0} cannot be used in a alternation of types; it is not a node kind", typePart)
                Else
                    typeList.Add(CType(foundType, ParseNodeKind))
                End If
            End If
        Next
 
        If typeList.Count = 1 Then
            Return typeList(0)
        End If
        Return typeList
    End Function
 
    Public Function ParseNodeKind(typeString As String, referencingNode As XNode) As Object
        If (typeString.Contains("|")) Then
            Dim typeList As New List(Of ParseNodeKind)
            For Each typePart As String In typeString.Split("|"c)
                Dim foundType = ParseNodeKind(typePart, referencingNode)
                If (TypeOf foundType Is List(Of ParseNodeKind)) Then
                    typeList.AddRange(CType(foundType, List(Of ParseNodeKind)))
                Else
                    If Not TypeOf foundType Is ParseNodeKind Then
                        ReportError(referencingNode, "{0} cannot be used in a alternation of types; it is not a node kind", typePart)
                    Else
                        typeList.Add(CType(foundType, ParseNodeKind))
                    End If
                End If
            Next
 
            Return typeList
        End If
 
        If typeString.StartsWith("@", StringComparison.Ordinal) Then
            Dim nodeTypeString = typeString.Substring(1)
            Dim nodeStructure As ParseNodeStructure = Nothing
            If Not NodeStructures.TryGetValue(nodeTypeString, nodeStructure) Then
                ReportError(referencingNode, "Unknown structure '@{0}'", nodeTypeString)
                Return Nothing
            End If
 
            Return nodeStructure.GetAllKinds()
        End If
 
        Dim nodeKindAlias As ParseNodeKindAlias = Nothing
        If Aliases.TryGetValue(typeString, nodeKindAlias) Then
            Return ParseNodeKind(nodeKindAlias.AliasKinds, referencingNode)
        End If
 
        Return ParseOneNodeKind(typeString, referencingNode)
    End Function
 
    ' Is this structure some base structure of another, or the same
    Public Function IsAncestorOrSame(parent As ParseNodeStructure, child As ParseNodeStructure) As Boolean
        Do
            If (parent Is child) Then
                Return True
            End If
 
            child = child.ParentStructure
        Loop While (child IsNot Nothing)
 
        Return False
    End Function
 
End Class
 
' Base class for things in the parse trees. Each one has a reference back 
' to the containing parse tree, and also the XML element it was loaded from.
Public MustInherit Class ParseTreeDefinition
    Private _parseTree As ParseTree
 
    Public Overridable Property ParseTree() As ParseTree
        Get
            Return Me._parseTree
        End Get
        Set(value As ParseTree)
            Me._parseTree = value
        End Set
    End Property
 
    ' The element this was loaded from. Primarily useful for getting line/col info for errors.
    Public Element As XElement
End Class
 
' Information defined in a node-structure element. Defines a node class in the parse tree.
Public Class ParseNodeStructure
    Inherits ParseTreeDefinition
 
    ' name of the structure.
    Public Name As String
 
    ' parent as a string.
    Public ParentStructureId As String
 
    Public ReadOnly Property ParentStructure() As ParseNodeStructure
        Get
            If String.IsNullOrEmpty(ParentStructureId) Then
                Return Nothing
            Else
                Dim nodeStructure As ParseNodeStructure = Nothing
                If Not ParseTree.NodeStructures.TryGetValue(ParentStructureId, nodeStructure) Then
                    ParseTree.ReportError(Element, "Unknown parent structure '{0}' for node-structure '{1}'", ParentStructureId, Name)
                    Return Nothing
                End If
                Return nodeStructure
            End If
        End Get
    End Property
 
    ' Information about the structure.
 
    Public Description As String
 
    Public Abstract As Boolean
 
    Public PartialClass As Boolean
 
    Public IsPredefined As Boolean
 
    Public IsRoot As Boolean
 
    Public IsTokenRoot, IsTriviaRoot As Boolean ' is this the root of tokens, trivia?
 
    Public NoFactory As Boolean ' if true, don't create factory method
 
    Public NodeKinds As List(Of ParseNodeKind)
 
    Public Fields As List(Of ParseNodeField)
 
    Public Children As List(Of ParseNodeChild)
 
    Public HasDerivedStructure As Boolean ' does this node have any nodes derived from it?
 
    Public ReadOnly InternalSyntaxFacts As Boolean
 
    Public ReadOnly HasDefaultFactory As Boolean
 
    Public IsToken As Boolean ' true if node derives from token root
    Public IsTrivia As Boolean ' true if node derives from trivia root
 
    Public ReadOnly Property IsTerminal As Boolean
        Get
            Return IsToken OrElse (Not ParseTree.HasAnyChildren(Me) AndAlso Not Me.Abstract)
        End Get
    End Property
 
    Public DefaultTrailingTrivia As String ' default trailing trivia for the simplified factory.
 
    ' Create a new structure in the give tree and load it from the give XElement.
    Public Sub New(el As XElement, tree As ParseTree)
        Me.ParseTree = tree
        Me.Element = el
 
        Name = el.@name
        ParentStructureId = el.@parent
        Description = el.<description>.Value
        DefaultTrailingTrivia = el.@<default-trailing-trivia>
 
        Abstract = If(CType(el.Attribute("abstract"), Boolean?), False)
        PartialClass = If(CType(el.Attribute("partial"), Boolean?), False)
        IsPredefined = If(CType(el.Attribute("predefined"), Boolean?), False)
        IsRoot = If(CType(el.Attribute("root"), Boolean?), False)
        IsTokenRoot = If(CType(el.Attribute("token-root"), Boolean?), False)
        IsTriviaRoot = If(CType(el.Attribute("trivia-root"), Boolean?), False)
        NoFactory = If(CType(el.Attribute("no-factory"), Boolean?), False)
        HasDefaultFactory = If(CType(el.Attribute("has-default-factory"), Boolean?), False)
        InternalSyntaxFacts = If(CType(el.Attribute("syntax-facts-internal"), Boolean?), False)
 
        NodeKinds = (From nk In el.<node-kind> Select New ParseNodeKind(nk, Me)).ToList()
        For Each nk In NodeKinds
            If tree.NodeKinds.ContainsKey(nk.Name) Then
                tree.ReportError(Element, "node-kind ""{0}"" already defined.", nk.Name)
            Else
                tree.NodeKinds.Add(nk.Name, nk)
            End If
        Next
        Fields = (From f In el.<field> Select New ParseNodeField(f, Me)).ToList()
        Children = (From c In el.<child> Select New ParseNodeChild(c, Me)).ToList()
    End Sub
 
    Public Function GetAllKinds() As List(Of ParseNodeKind)
        Return New List(Of ParseNodeKind)(From kvPair In ParseTree.NodeKinds Where ParseTree.IsAncestorOrSame(Me, kvPair.Value.NodeStructure) Select kvPair.Value)
    End Function
 
    Public Function DerivesFrom(baseClassName As String) As Boolean
        Dim nodeStructure As ParseNodeStructure = Me
        While nodeStructure IsNot Nothing
            If String.Compare(nodeStructure.Name, baseClassName, True) = 0 Then
                Return True
            End If
            nodeStructure = nodeStructure.ParentStructure
        End While
        Return False
    End Function
 
    Public Overrides Function ToString() As String
        Return Name
    End Function
End Class
 
' Defines a single node kind in the tree. Every node kind is associated with a structure, but
' a structure can have multiple node kinds. Kinds must be unique in the whole tree.
Public Class ParseNodeKind
    Inherits ParseTreeDefinition
 
    Public Name As String
 
    Public StructureId As String
 
    Public TokenText As String
 
    Public NoFactory As Boolean ' if true, don't create factory method for this kind.
 
    Public ReadOnly Property NodeStructure() As ParseNodeStructure
        Get
            If String.IsNullOrEmpty(StructureId) Then
                Return Nothing
            Else
                Dim parseNodeStructure As ParseNodeStructure = Nothing
                If Not ParseTree.NodeStructures.TryGetValue(StructureId, parseNodeStructure) Then
                    ParseTree.ReportError(Element, "Unknown structure '{0}' for node-kind '{1}'", StructureId, Name)
                    Return Nothing
                End If
                Return parseNodeStructure
            End If
        End Get
    End Property
 
    Public Description As String
 
    Public Sub New(el As XElement, struct As ParseNodeStructure)
        Me.ParseTree = struct.ParseTree
        Me.Element = el
 
        Name = el.@name
        TokenText = el.@<token-text>
        NoFactory = If(CType(el.Attribute("no-factory"), Boolean?), False)
        StructureId = struct.Name
        Description = If(el.<description>.Value, struct.Description)
    End Sub
End Class
 
' Defines an alias for one or more node kinds.
Public Class ParseNodeKindAlias
    Inherits ParseTreeDefinition
 
    Public Name As String
 
    Public AliasKinds As String
 
    Public Description As String
 
    Public Sub New(el As XElement, tree As ParseTree)
        ParseTree = tree
        Me.Element = el
 
        Name = el.@name
        AliasKinds = el.@alias
        Description = el.<description>.Value
    End Sub
End Class
 
' A field in a node structure. A field is a property that stores data like integer,
' text, etc, not a child node. Its type can be a simple type or an enumeration type.
Public Class ParseNodeField
    Inherits ParseTreeDefinition
 
    Public ReadOnly Name As String
 
    Public ReadOnly ContainingStructure As ParseNodeStructure
 
    Public ReadOnly IsOptional As Boolean
 
    Public ReadOnly FieldTypeId As String
 
    Public ReadOnly Description As String
 
    Public Sub New(el As XElement, struct As ParseNodeStructure)
        Me.ParseTree = struct.ParseTree
        Me.Element = el
        Me.ContainingStructure = struct
 
        Name = el.@name
        IsOptional = If(CType(el.Attribute("optional"), Boolean?), False)
        FieldTypeId = el.@type
        Description = el.<description>.Value
    End Sub
 
    ' Gets the field type. Could return a SimpleType, Enumeration 
    Public ReadOnly Property FieldType() As Object
        Get
            Select Case FieldTypeId.ToLowerInvariant()
                Case "boolean"
                    Return SimpleType.Bool
                Case "text"
                    Return SimpleType.Text
                Case "character"
                    Return SimpleType.Character
                Case "int32"
                    Return SimpleType.Int32
                Case "uint32"
                    Return SimpleType.UInt32
                Case "int64"
                    Return SimpleType.Int64
                Case "uint64"
                    Return SimpleType.UInt64
                Case "float32"
                    Return SimpleType.Float32
                Case "float64"
                    Return SimpleType.Float64
                Case "decimal"
                    Return SimpleType.Decimal
                Case "datetime"
                    Return SimpleType.DateTime
                Case "textspan"
                    Return SimpleType.TextSpan
                Case "nodekind"
                    Return SimpleType.NodeKind
                Case Else
                    Return ParseTree.ParseEnumType(FieldTypeId, Element)
            End Select
        End Get
    End Property
 
End Class
 
' Defines a child node with a node structure. A child can be a single child
' node of a list of child nodes.
Public Class ParseNodeChild
    Inherits ParseTreeDefinition
 
    Public ReadOnly Name As String
 
    Public ReadOnly ContainingStructure As ParseNodeStructure
 
    Public ReadOnly IsOptional As Boolean
 
    Public ReadOnly MinCount As Integer
 
    Public ReadOnly IsList As Boolean
 
    Public ReadOnly IsSeparated As Boolean
 
    Public ReadOnly SeparatorsName As String
 
    Private ReadOnly _childKindNames As New Dictionary(Of String, List(Of String))
    Private _childKind As Object
 
    Public ReadOnly SeparatorsTypeId As String
 
    Public ReadOnly Description As String
 
    Public ReadOnly Order As Single
 
    Public ReadOnly NotInFactory As Boolean
 
    Public ReadOnly GenerateWith As Boolean
 
    Public ReadOnly InternalSyntaxFacts As Boolean
 
    Public KindForNodeKind As Dictionary(Of String, ParseNodeKind)
 
    Private ReadOnly _defaultChildKindName As String
    Private _defaultChildKind As ParseNodeKind
 
    Public Sub New(el As XElement, struct As ParseNodeStructure)
        Me.ParseTree = struct.ParseTree
        Me.Element = el
        Me.ContainingStructure = struct
        Single.TryParse(el.@order, NumberStyles.Any, CultureInfo.InvariantCulture, Me.Order)
        Name = el.@name
        SeparatorsName = el.@<separator-name>
        SeparatorsTypeId = el.@<separator-kind>
        IsList = If(CType(el.Attribute("list"), Boolean?), False)
        IsSeparated = el.@<separator-kind> <> ""
        IsOptional = If(CType(el.Attribute("optional"), Boolean?), False)
        MinCount = If(CType(el.Attribute("min-count"), Integer?), 0)
        Description = el.<description>.Value
        NotInFactory = If(CType(el.Attribute("not-in-factory"), Boolean?), False)
        GenerateWith = If(CType(el.Attribute("generate-with"), Boolean?), False)
        InternalSyntaxFacts = If(CType(el.Attribute("syntax-facts-internal"), Boolean?), False)
        _defaultChildKindName = CType(el.Attribute("default-kind"), String)
 
        For Each kind In el.<kind>
            ' The kind may be duplicated
            Dim nodeKinds As List(Of String) = Nothing
            If _childKindNames.TryGetValue(kind.@name, nodeKinds) Then
                nodeKinds.Add(kind.@<node-kind>)
            Else
                nodeKinds = New List(Of String)
                nodeKinds.Add(kind.@<node-kind>)
                _childKindNames.Add(kind.@name, nodeKinds)
            End If
        Next
        If _childKindNames.Count = 0 Then
            Dim kindsString = el.@kind
            For Each kind As String In kindsString.Split("|"c)
                _childKindNames.Add(kind, Nothing) 'New List(Of String)(From nodeKind In struct.NodeKinds Select nodeKind.Name))
            Next
        ElseIf el.@kind IsNot Nothing Then
            ParseTree.ReportError(el, "Cannot have both kind attribute on child and also have kind sub element in child.")
        End If
    End Sub
 
    Public ReadOnly Property ChildKind(kind As String) As ParseNodeKind
        Get
            If KindForNodeKind Is Nothing Then
                KindForNodeKind = New Dictionary(Of String, ParseNodeKind)
                For Each key In _childKindNames.Keys
                    Dim nodeNames = _childKindNames(key)
                    If nodeNames IsNot Nothing Then
                        Dim child = ParseTree.ParseOneNodeKind(key, Element)
                        For Each nodeName In nodeNames
                            Dim node = ParseTree.ParseOneNodeKind(nodeName, Element)
                            KindForNodeKind.Add(node.Name, child)
                        Next
                    End If
                Next
            End If
            Dim result As ParseNodeKind = Nothing
            KindForNodeKind.TryGetValue(kind, result)
            Return result
        End Get
    End Property
 
    Public ReadOnly Property ChildKind(containerKinds As IList(Of ParseNodeKind)) As List(Of ParseNodeKind)
        Get
            Dim allChildkinds As List(Of ParseNodeKind) = TryCast(ChildKind(), List(Of ParseNodeKind))
            If allChildkinds Is Nothing Then
                Return Nothing
            End If
 
            Dim childkindsForContainer = New List(Of ParseNodeKind)
            For Each containerKind In containerKinds
                Dim propertyKind = ChildKind(containerKind.Name)
                If propertyKind IsNot Nothing Then
                    childkindsForContainer.Add(propertyKind)
                End If
            Next
            If childkindsForContainer.Count > 0 Then
                Return childkindsForContainer
            End If
 
            Return allChildkinds
        End Get
    End Property
 
    ' Gets the child type. Could return a NodeKind, List(NodeKind) containing the allowable node kinds of the child.
    Public ReadOnly Property ChildKind() As Object
        Get
            If _childKind Is Nothing Then
                Dim names(_childKindNames.Count - 1) As String
                _childKindNames.Keys.CopyTo(names, 0)
                _childKind = ParseTree.ParseNodeKind(names, Element)
            End If
            Return _childKind
        End Get
    End Property
 
    Public Function WithChildKind(childKind As Object) As ParseNodeChild
        Dim copy = New ParseNodeChild(Me.Element, Me.ContainingStructure)
        copy._childKind = childKind
        Return copy
    End Function
 
    Public ReadOnly Property DefaultChildKind() As ParseNodeKind
        Get
            If _defaultChildKind Is Nothing AndAlso _defaultChildKindName IsNot Nothing Then
                _defaultChildKind = CType(ParseTree.ParseNodeKind(_defaultChildKindName, Element), ParseNodeKind)
            End If
            Return _defaultChildKind
        End Get
    End Property
 
    ' Gets the separators type. Could return a NodeKind, List(NodeKind) containing the allowable node kinds of the child.
    Public ReadOnly Property SeparatorsKind() As Object
        Get
            Return ParseTree.ParseNodeKind(SeparatorsTypeId, Element)
        End Get
    End Property
 
    Public Overrides Function ToString() As String
        Return Name
    End Function
 
End Class
 
' Defines an enumeration type, so that fields of this type can be defined.
Public Class ParseEnumeration
    Inherits ParseTreeDefinition
 
    Public Name As String
 
    Public IsFlags As Boolean
 
    Public Description As String
 
    Public Enumerators As List(Of ParseEnumerator)
 
    Public Sub New(el As XElement, tree As ParseTree)
        ParseTree = tree
        Me.Element = el
 
        Name = el.@name
        IsFlags = If(CType(el.Attribute("flags"), Boolean?), False)
        Description = el.<description>.Value
 
        Enumerators = (From en In el.<enumerators>.<enumerator> Select New ParseEnumerator(en, Me)).ToList()
    End Sub
End Class
 
' Defines a single enumerator inside an enumeration type.
Public Class ParseEnumerator
    Public Name As String
 
    Public ValueString As String
 
    Public Description As String
 
    Public Sub New(el As XElement, enumeration As ParseEnumeration)
        Name = el.@name
        ValueString = el.@hexvalue
        Description = el.<description>.Value
    End Sub
 
    Public ReadOnly Property Value() As Long
        Get
            Return Convert.ToInt64(ValueString, 16)
        End Get
    End Property
 
End Class
 
' THe kinds of simple types for fields.
Public Enum SimpleType
    Bool
    Text
    Character
    Int32
    UInt32
    Int64
    UInt64
    Float32
    Float64
    [Decimal]
    DateTime
    TextSpan
    NodeKind
End Enum