|
// 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.
using System;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common;
/// <summary>
/// Root of the embedded language syntax hierarchy. EmbeddedSyntaxNodes are very similar to
/// Roslyn Red-Nodes in concept, though there are differences for ease of implementation.
///
/// Similarities:
/// 1. Fully representative of the original source. All source VirtualChars are contained
/// in the Regex nodes.
/// 2. Specific types for Nodes, Tokens and Trivia.
/// 3. Uniform ways of deconstructing Nodes (i.e. ChildCount + ChildAt).
///
/// Differences:
/// Note: these differences are not required, and can be changed if felt to be valuable.
/// 1. No parent pointers. These have not been needed yet.
/// 2. No Update methods. These have not been needed yet.
/// 3. No direct ways to get Positions/Spans of node/token/trivia. Instead, that information can
/// be acquired from the VirtualChars contained within those constructs. This does mean that
/// an empty node (for example, an empty RegexSequenceNode) effect has no way to simply ascertain
/// its location. So far that hasn't been a problem.
/// 4. No null nodes. Haven't been needed so far, and it keeps things extremely simple. For
/// example where Roslyn might have chosen an optional null child, the Regex hierarchy just
/// has multiple nodes. For example there are distinct nodes to represent the very similar
/// {a} {a,} {a,b} constructs.
/// </summary>
internal abstract class EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode>
where TSyntaxKind : struct
where TSyntaxNode : EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode>
{
public readonly TSyntaxKind Kind;
private TextSpan? _fullSpan;
protected EmbeddedSyntaxNode(TSyntaxKind kind)
{
Debug.Assert((int)(object)kind != 0);
Kind = kind;
}
internal abstract int ChildCount { get; }
internal abstract EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> ChildAt(int index);
public EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> this[int index] => ChildAt(index);
public EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> this[Index index] => this[index.GetOffset(this.ChildCount)];
public TextSpan GetSpan()
{
var start = int.MaxValue;
var end = 0;
this.GetSpan(ref start, ref end);
return TextSpan.FromBounds(start, end);
}
public TextSpan? GetFullSpan()
=> _fullSpan ??= ComputeFullSpan();
private TextSpan? ComputeFullSpan()
{
var start = ComputeStart();
var end = ComputeEnd();
if (start == null || end == null)
return null;
return TextSpan.FromBounds(start.Value, end.Value);
int? ComputeStart()
{
for (int i = 0, n = ChildCount; i < n; i++)
{
var child = ChildAt(i);
var span = child.GetFullSpan();
if (span != null)
return span.Value.Start;
}
return null;
}
int? ComputeEnd()
{
for (var i = ChildCount - 1; i >= 0; i--)
{
var child = ChildAt(i);
var span = child.GetFullSpan();
if (span != null)
return span.Value.End;
}
return null;
}
}
private void GetSpan(ref int start, ref int end)
{
foreach (var child in this)
{
if (child.IsNode)
{
child.Node.GetSpan(ref start, ref end);
}
else
{
var token = child.Token;
if (!token.IsMissing)
{
start = Math.Min(token.VirtualChars[0].Span.Start, start);
end = Math.Max(token.VirtualChars.Last().Span.End, end);
}
}
}
}
public bool Contains(VirtualChar virtualChar)
{
foreach (var child in this)
{
if (child.IsNode)
{
if (child.Node.Contains(virtualChar))
{
return true;
}
}
else
{
if (child.Token.VirtualChars.Contains(virtualChar))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Returns the string representation of this node, not including its leading and trailing trivia.
/// </summary>
/// <returns>The string representation of this node, not including its leading and trailing trivia.</returns>
/// <remarks>The length of the returned string is always the same as Span.Length</remarks>
public override string ToString()
{
using var _ = PooledStringBuilder.GetInstance(out var sb);
WriteTo(sb, leading: false, trailing: false);
return sb.ToString();
}
/// <summary>
/// Returns full string representation of this node including its leading and trailing trivia.
/// </summary>
/// <returns>The full string representation of this node including its leading and trailing trivia.</returns>
/// <remarks>The length of the returned string is always the same as FullSpan.Length</remarks>
public string ToFullString()
{
using var _ = PooledStringBuilder.GetInstance(out var sb);
WriteTo(sb, leading: true, trailing: true);
return sb.ToString();
}
/// <summary>
/// Writes the node to a stringbuilder.
/// </summary>
/// <param name="leading">If false, leading trivia will not be added</param>
/// <param name="trailing">If false, trailing trivia will not be added</param>
public void WriteTo(StringBuilder sb, bool leading, bool trailing)
{
for (var i = 0; i < this.ChildCount; i++)
{
var child = this[i];
var currentLeading = leading || i > 0;
var curentTrailing = trailing || i < (this.ChildCount - 1);
if (child.IsNode)
{
child.Node.WriteTo(sb, currentLeading, curentTrailing);
}
else
{
child.Token.WriteTo(sb, currentLeading, curentTrailing);
}
}
}
public Enumerator GetEnumerator()
=> new(this);
public struct Enumerator
{
private readonly EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode> _node;
private readonly int _childCount;
private int _currentIndex;
public Enumerator(EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode> node)
{
_node = node;
_childCount = _node.ChildCount;
_currentIndex = -1;
Current = default;
}
public EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> Current { get; private set; }
public bool MoveNext()
{
_currentIndex++;
if (_currentIndex >= _childCount)
{
Current = default;
return false;
}
Current = _node.ChildAt(_currentIndex);
return true;
}
}
}
|