| File: Parser\Directives.cs | Web Access |
| Project: src\roslyn\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp) |
// 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.Diagnostics; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax { [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] internal readonly struct Directive { private readonly DirectiveTriviaSyntax _node; internal Directive(DirectiveTriviaSyntax node) { _node = node; } public SyntaxKind Kind { get { return _node.Kind; } } public bool IncrementallyEquivalent(Directive other) { if (this.Kind != other.Kind) { return false; } bool isActive = this.IsActive; bool otherIsActive = other.IsActive; // states of inactive directives don't matter if (!isActive && !otherIsActive) { return true; } if (isActive != otherIsActive) { return false; } switch (this.Kind) { case SyntaxKind.DefineDirectiveTrivia: case SyntaxKind.UndefDirectiveTrivia: return this.GetIdentifier() == other.GetIdentifier(); case SyntaxKind.IfDirectiveTrivia: case SyntaxKind.ElifDirectiveTrivia: case SyntaxKind.ElseDirectiveTrivia: return this.BranchTaken == other.BranchTaken; default: return true; } } // Can't be private as it's called by DirectiveStack in its GetDebuggerDisplay() internal string GetDebuggerDisplay() { var writer = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture); _node.WriteTo(writer, false, false); return writer.ToString(); } internal string? GetIdentifier() { switch (_node.Kind) { case SyntaxKind.DefineDirectiveTrivia: return ((DefineDirectiveTriviaSyntax)_node).Name.ValueText; case SyntaxKind.UndefDirectiveTrivia: return ((UndefDirectiveTriviaSyntax)_node).Name.ValueText; default: return null; } } internal bool IsActive { get { return _node.IsActive; } } internal bool BranchTaken { get { var branching = _node as BranchingDirectiveTriviaSyntax; if (branching != null) { return branching.BranchTaken; } return false; } } } internal enum DefineState { Defined, Undefined, Unspecified } [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] internal readonly struct DirectiveStack { public static readonly DirectiveStack Empty = new DirectiveStack(ConsList<Directive>.Empty, seenAnyIfDirectives: false); private readonly ConsList<Directive>? _directives; private readonly bool _seenAnyIfDirectives; private DirectiveStack(ConsList<Directive>? directives, bool seenAnyIfDirectives) { _directives = directives; _seenAnyIfDirectives = seenAnyIfDirectives; } public static void InterlockedInitialize(ref DirectiveStack location, DirectiveStack value) => Interlocked.CompareExchange(ref Unsafe.AsRef(in location._directives), value._directives, null); public bool IsNull { get { return _directives == null; } } public bool IsEmpty { get { return _directives == ConsList<Directive>.Empty; } } public bool SeenAnyIfDirectives => _seenAnyIfDirectives; public DefineState IsDefined(string id) { for (var current = _directives; current != null && current.Any(); current = current.Tail) { switch (current.Head.Kind) { case SyntaxKind.DefineDirectiveTrivia: if (current.Head.GetIdentifier() == id) { return DefineState.Defined; } break; case SyntaxKind.UndefDirectiveTrivia: if (current.Head.GetIdentifier() == id) { return DefineState.Undefined; } break; case SyntaxKind.ElifDirectiveTrivia: case SyntaxKind.ElseDirectiveTrivia: // Skip directives from previous branches of the same #if. do { current = current.Tail; if (current == null || !current.Any()) { return DefineState.Unspecified; } } while (current.Head.Kind != SyntaxKind.IfDirectiveTrivia); break; } } return DefineState.Unspecified; } // true if any previous section of the closest #if has its branch taken public bool PreviousBranchTaken() { for (var current = _directives; current != null && current.Any(); current = current.Tail) { if (current.Head.BranchTaken) { return true; } else if (current.Head.Kind == SyntaxKind.IfDirectiveTrivia) { return false; } } return false; } public bool HasUnfinishedIf() { var prev = GetPreviousIfElifElseOrRegion(_directives); return prev != null && prev.Any() && prev.Head.Kind != SyntaxKind.RegionDirectiveTrivia; } public bool HasPreviousIfOrElif() { var prev = GetPreviousIfElifElseOrRegion(_directives); return prev != null && prev.Any() && (prev.Head.Kind == SyntaxKind.IfDirectiveTrivia || prev.Head.Kind == SyntaxKind.ElifDirectiveTrivia); } public bool HasUnfinishedRegion() { var prev = GetPreviousIfElifElseOrRegion(_directives); return prev != null && prev.Any() && prev.Head.Kind == SyntaxKind.RegionDirectiveTrivia; } public DirectiveStack Add(Directive directive) { switch (directive.Kind) { case SyntaxKind.EndIfDirectiveTrivia: var prevIf = GetPreviousIf(_directives); if (prevIf == null || !prevIf.Any()) { goto default; // no matching if directive !! leave directive alone } RoslynDebug.AssertNotNull(_directives); // If 'prevIf' isn't null, then '_directives' wasn't null. return new DirectiveStack(CompleteIf(_directives, out _), seenAnyIfDirectives: _seenAnyIfDirectives); case SyntaxKind.EndRegionDirectiveTrivia: var prevRegion = GetPreviousRegion(_directives); if (prevRegion == null || !prevRegion.Any()) { goto default; // no matching region directive !! leave directive alone } RoslynDebug.AssertNotNull(_directives); // If 'prevRegion' isn't null, then '_directives' wasn't null. return new DirectiveStack(CompleteRegion(_directives), seenAnyIfDirectives: _seenAnyIfDirectives); // remove region directives from stack but leave everything else default: return new DirectiveStack(new ConsList<Directive>(directive, _directives ?? ConsList<Directive>.Empty), seenAnyIfDirectives: _seenAnyIfDirectives || directive.Kind is SyntaxKind.IfDirectiveTrivia); } } // removes unfinished if & related directives from stack and leaves active branch directives private static ConsList<Directive> CompleteIf(ConsList<Directive> stack, out bool include) { // if we get to the top, the default rule is to include anything that follows if (!stack.Any()) { include = true; return stack; } // if we reach the #if directive, then we stop unwinding and start // rebuilding the stack w/o the #if/#elif/#else/#endif directives // only including content from sections that are considered included if (stack.Head.Kind == SyntaxKind.IfDirectiveTrivia) { include = stack.Head.BranchTaken; return stack.Tail; } var newStack = CompleteIf(stack.Tail, out include); switch (stack.Head.Kind) { case SyntaxKind.ElifDirectiveTrivia: case SyntaxKind.ElseDirectiveTrivia: include = stack.Head.BranchTaken; break; default: if (include) { newStack = new ConsList<Directive>(stack.Head, newStack); } break; } return newStack; } // removes region directives from stack but leaves everything else private static ConsList<Directive> CompleteRegion(ConsList<Directive> stack) { // if we get to the top, the default rule is to include anything that follows if (!stack.Any()) { return stack; } if (stack.Head.Kind == SyntaxKind.RegionDirectiveTrivia) { return stack.Tail; } var newStack = CompleteRegion(stack.Tail); newStack = new ConsList<Directive>(stack.Head, newStack); return newStack; } private static ConsList<Directive>? GetPreviousIf(ConsList<Directive>? directives) { var current = directives; while (current != null && current.Any()) { switch (current.Head.Kind) { case SyntaxKind.IfDirectiveTrivia: return current; } current = current.Tail; } return current; } private static ConsList<Directive>? GetPreviousIfElifElseOrRegion(ConsList<Directive>? directives) { var current = directives; while (current != null && current.Any()) { switch (current.Head.Kind) { case SyntaxKind.IfDirectiveTrivia: case SyntaxKind.ElifDirectiveTrivia: case SyntaxKind.ElseDirectiveTrivia: case SyntaxKind.RegionDirectiveTrivia: return current; } current = current.Tail; } return current; } private static ConsList<Directive>? GetPreviousRegion(ConsList<Directive>? directives) { var current = directives; while (current != null && current.Any() && current.Head.Kind != SyntaxKind.RegionDirectiveTrivia) { current = current.Tail; } return current; } internal string GetDebuggerDisplay() { if (IsNull) { return "<null>"; } if (IsEmpty) { return "[]"; } var sb = new StringBuilder(); for (var current = _directives; current != null && current.Any(); current = current.Tail) { if (sb.Length > 0) { sb.Insert(0, " | "); } sb.Insert(0, current.Head.GetDebuggerDisplay()); } return sb.ToString(); } public bool IncrementallyEquivalent(DirectiveStack other) { var mine = SkipInsignificantDirectives(_directives); var theirs = SkipInsignificantDirectives(other._directives); bool mineHasAny = mine != null && mine.Any(); bool theirsHasAny = theirs != null && theirs.Any(); while (mineHasAny && theirsHasAny) { if (!mine!.Head.IncrementallyEquivalent(theirs!.Head)) { return false; } mine = SkipInsignificantDirectives(mine.Tail); theirs = SkipInsignificantDirectives(theirs.Tail); mineHasAny = mine != null && mine.Any(); theirsHasAny = theirs != null && theirs.Any(); } return mineHasAny == theirsHasAny; } private static ConsList<Directive>? SkipInsignificantDirectives(ConsList<Directive>? directives) { for (; directives != null && directives.Any(); directives = directives.Tail) { switch (directives.Head.Kind) { case SyntaxKind.IfDirectiveTrivia: case SyntaxKind.ElifDirectiveTrivia: case SyntaxKind.ElseDirectiveTrivia: case SyntaxKind.EndIfDirectiveTrivia: case SyntaxKind.DefineDirectiveTrivia: case SyntaxKind.UndefDirectiveTrivia: case SyntaxKind.RegionDirectiveTrivia: case SyntaxKind.EndRegionDirectiveTrivia: return directives; } } return directives; } } }