| File: Language\Syntax\GreenNode.cs | Web Access |
| Project: src\roslyn\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace Microsoft.AspNetCore.Razor.Language.Syntax; [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] internal abstract partial class GreenNode { private static readonly ConditionalWeakTable<GreenNode, RazorDiagnostic[]> s_diagnosticsTable = new(); private static readonly RazorDiagnostic[] s_emptyDiagnostics = []; private int _width; private byte _slotCount; protected GreenNode(SyntaxKind kind) { Kind = kind; } protected GreenNode(SyntaxKind kind, int width) : this(kind) { _width = width; } protected GreenNode(SyntaxKind kind, RazorDiagnostic[]? diagnostics) : this(kind, 0, diagnostics) { } protected GreenNode(SyntaxKind kind, int width, RazorDiagnostic[]? diagnostics) : this(kind, width) { if (diagnostics?.Length > 0) { Flags |= NodeFlags.ContainsDiagnostics; s_diagnosticsTable.Add(this, diagnostics); } } protected void AdjustFlagsAndWidth(GreenNode node) { if (node == null) { return; } Flags |= node.Flags & NodeFlags.InheritMask; _width += node.Width; } #region Kind internal SyntaxKind Kind { get; } internal virtual bool IsList => false; internal virtual bool IsToken => false; #endregion public int Width => _width; #region Slots public int SlotCount { get { int count = _slotCount; if (count == byte.MaxValue) { count = GetSlotCount(); } return count; } protected set { _slotCount = (byte)value; } } internal abstract GreenNode? GetSlot(int index); internal GreenNode GetRequiredSlot(int index) { var node = GetSlot(index); Debug.Assert(node is not null); return node; } // for slot counts >= byte.MaxValue protected virtual int GetSlotCount() { return _slotCount; } public virtual int GetSlotOffset(int index) { var offset = 0; for (var i = 0; i < index; i++) { var child = GetSlot(i); if (child != null) { offset += child.Width; } } return offset; } public virtual int FindSlotIndexContainingOffset(int offset) { Debug.Assert(0 <= offset && offset < Width); int i; var accumulatedWidth = 0; for (i = 0; ; i++) { Debug.Assert(i < SlotCount); var child = GetSlot(i); if (child != null) { accumulatedWidth += child.Width; if (offset < accumulatedWidth) { break; } } } return i; } #endregion #region Flags public NodeFlags Flags { get; protected set; } internal void SetFlags(NodeFlags flags) { Flags |= flags; } internal void ClearFlags(NodeFlags flags) { Flags &= ~flags; } internal virtual bool IsMissing => (Flags & NodeFlags.IsMissing) != 0; public bool ContainsDiagnostics => (Flags & NodeFlags.ContainsDiagnostics) != 0; #endregion #region Diagnostics internal abstract GreenNode SetDiagnostics(RazorDiagnostic[]? diagnostics); internal RazorDiagnostic[] GetDiagnostics() { if (ContainsDiagnostics) { if (s_diagnosticsTable.TryGetValue(this, out var diagnostics)) { return diagnostics; } } return s_emptyDiagnostics; } #endregion #region Text private string GetDebuggerDisplay() => $"{GetType().Name}<{Kind}>"; public override string ToString() { // Simple case: Zero width is just an empty string. if (_width == 0) { return string.Empty; } // Special case: See if there's just a single non-zero-width descendant token. // If so, we can just return the content of that token rather than creating a new string for it. foreach (var token in Tokens()) { if (token.Width == _width) { // If this token has the same width as this node, just return it's content. return token.Content; } // At this point, if this is a zero-width token, we know there must be more // non-zero-width tokens. Break out of the loop to allocate a new string with all // of the content. if (token.Width != 0) { break; } // This was just a zero-width token - continue looping. } // At this point, we know that we have multiple tokens and need to allocate a string. return string.Create(length: _width, this, static (span, node) => { foreach (var token in node.Tokens()) { var content = token.Content.AsSpan(); if (content.Length > 0) { content.CopyTo(span); span = span[content.Length..]; } } Debug.Assert(span.IsEmpty); }); } #endregion #region Equivalence public virtual bool IsEquivalentTo([NotNullWhen(true)] GreenNode? other) { if (this == other) { return true; } if (other == null) { return false; } return EquivalentToInternal(this, other); } private static bool EquivalentToInternal(GreenNode node1, GreenNode node2) { if (node1.Kind != node2.Kind) { // A single-element list is usually represented as just a single node, // but can be represented as a List node with one child. Move to that // child if necessary. if (node1.IsList && node1.SlotCount == 1) { node1 = node1.GetRequiredSlot(0); } if (node2.IsList && node2.SlotCount == 1) { node2 = node2.GetRequiredSlot(0); } if (node1.Kind != node2.Kind) { return false; } } if (node1.Width != node2.Width) { return false; } var n = node1.SlotCount; if (n != node2.SlotCount) { return false; } for (var i = 0; i < n; i++) { var node1Child = node1.GetSlot(i); var node2Child = node2.GetSlot(i); if (node1Child != null && node2Child != null && !node1Child.IsEquivalentTo(node2Child)) { return false; } } return true; } #endregion #region Factories public SyntaxNode CreateRed() { return CreateRed(null, 0); } internal abstract SyntaxNode CreateRed(SyntaxNode? parent, int position); #endregion public TokenEnumerable Tokens() => new(this); public Enumerator GetEnumerator() => new(this); public abstract TResult Accept<TResult>(InternalSyntax.SyntaxVisitor<TResult> visitor); public abstract void Accept(InternalSyntax.SyntaxVisitor visitor); }