/// <summary>
        /// Returns the CodeFragment at the given position if such a fragment exists and null otherwise.
        /// If the given position is equal to the end of the fragment, that fragment is returned if includeEnd is set to true.
        /// If a fragment is determined for the given position, returns the corresponding token index as out parameter.
        /// Note that token indices are no longer valid as soon as the file is modified (possibly e.g. by queued background processing).
        /// Any query or attempt operation on the returned token index may result in an exception once it lost its validity.
        /// Returns null if the given file or the specified position is null,
        /// or if the specified position is not within the current Content range.
        /// </summary>
        public static CodeFragment?TryGetFragmentAt(
            this FileContentManager file,
            Position?pos,
            out CodeFragment.TokenIndex?tIndex,
            bool includeEnd = false)
        {
            tIndex = null;
            if (file == null || pos == null || !file.ContainsPosition(pos))
            {
                return(null);
            }
            var start    = pos.Line;
            var previous = file.GetTokenizedLine(start).Where(token => token.Range.Start.Column <= pos.Column).ToImmutableArray();

            while (!previous.Any() && --start >= 0)
            {
                previous = file.GetTokenizedLine(start);
            }
            if (!previous.Any())
            {
                return(null);
            }

            var lastPreceding = previous.Last().WithLineNumOffset(start);
            var overlaps      = includeEnd
                ? pos <= lastPreceding.Range.End
                : pos < lastPreceding.Range.End;

            tIndex = overlaps ? new CodeFragment.TokenIndex(file, start, previous.Length - 1) : null;
            return(overlaps ? lastPreceding : null);
        }
        /// <summary>
        /// Returns true if the given file contains any tokens overlapping with the given fragment.
        /// The range of the tokens in the file is assumed to be relative to their start line (the index at which they are listed),
        /// whereas the range of the given fragment is assumed to be the absolute range.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="range"/> is not a valid range within <paramref name="file"/>.</exception>
        internal static bool ContainsTokensOverlappingWith(this FileContentManager file, Range range)
        {
            if (!file.ContainsRange(range))
            {
                throw new ArgumentOutOfRangeException(nameof(range));
            }

            var(start, end) = (range.Start.Line, range.End.Line);
            if (start != end && file.GetTokenizedLines(start + 1, end - start - 1).SelectMany(x => x).Any())
            {
                return(true);
            }

            var inRange = file.GetTokenizedLine(start).Where(TokensAfter(Position.Create(0, range.Start.Column))); // checking tokens overlapping with range.Start below

            inRange = start == end
                ? inRange.Where(TokensStartingBefore(Position.Create(0, range.End.Column)))
                : inRange.Concat(file.GetTokenizedLine(end).Where(TokensStartingBefore(Position.Create(0, range.End.Column))));

            if (inRange.Any())
            {
                QsCompilerError.Raise($"{range.DiagnosticString()} overlaps for start = {start}, end = {end}, \n\n" +
                                      $"{string.Join("\n", file.GetTokenizedLine(start).Select(x => $"{x.Range.DiagnosticString()}"))},\n\n " +
                                      $"{string.Join("\n", file.GetTokenizedLine(end).Select(x => $"{x.Range.DiagnosticString()}"))},");
                return(true);
            }

            var overlapsWithStart = file.TryGetFragmentAt(range.Start, out _);

            return(overlapsWithStart != null);
        }
Exemple #3
0
        /// <summary>
        /// Returns all code fragments in the specified file that overlap with the given range.
        /// Returns an empty sequence if any of the given arguments is null.
        /// </summary>
        private static IEnumerable <CodeFragment> FragmentsOverlappingWithRange(this FileContentManager file, LSP.Range range)
        {
            if (file == null || range?.Start == null || range.End == null)
            {
                return(Enumerable.Empty <CodeFragment>());
            }
            var(start, end) = (range.Start.Line, range.End.Line);

            var fragAtStart = file.TryGetFragmentAt(range.Start, out var _, includeEnd: true);
            var inRange     = file.GetTokenizedLine(start).Select(t => t.WithUpdatedLineNumber(start)).Where(ContextBuilder.TokensAfter(range.Start)); // does not include fragAtStart

            inRange = start == end
                ? inRange.Where(ContextBuilder.TokensStartingBefore(range.End))
                : inRange.Concat(file.GetTokenizedLines(start + 1, end - start - 1).SelectMany((x, i) => x.Select(t => t.WithUpdatedLineNumber(start + 1 + i))))
                      .Concat(file.GetTokenizedLine(end).Select(t => t.WithUpdatedLineNumber(end)).Where(ContextBuilder.TokensStartingBefore(range.End)));

            var fragments = ImmutableArray.CreateBuilder <CodeFragment>();

            if (fragAtStart != null)
            {
                fragments.Add(fragAtStart);
            }
            fragments.AddRange(inRange);
            return(fragments.ToImmutableArray());
        }
        /// <summary>
        /// Returns the token index at, or the closest token index before, the given position. Returns null if there is
        /// no token at or before the given position.
        /// </summary>
        /// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the position is invalid.</exception>
        private static CodeFragment.TokenIndex GetTokenAtOrBefore(FileContentManager file, Position position)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (!Utils.IsValidPosition(position))
            {
                throw new ArgumentException(nameof(position));
            }

            var line   = position.Line;
            var tokens = file.GetTokenizedLine(line);

            // If the current line is empty, find the last non-empty line before it.
            while (tokens.IsEmpty && line > 0)
            {
                tokens = file.GetTokenizedLine(--line);
            }

            var index = tokens.TakeWhile(ContextBuilder.TokensUpTo(position)).Count() - 1;

            if (index == -1)
            {
                return(null);
            }
            return(new CodeFragment.TokenIndex(file, line, index));
        }
        /// <summary>
        /// Returns the CodeFragment at the given position if such a fragment exists and null otherwise.
        /// If the given position is equal to the end of the fragment, that fragment is returned if includeEnd is set to true.
        /// If a fragment is determined for the given position, returns the corresponding token index as out parameter.
        /// Note that token indices are no longer valid as soon as the file is modified (possibly e.g. by queued background processing).
        /// Any query or attempt operation on the returned token index may result in an exception once it lost its validity.
        /// Returns null if the given file or the specified position is null,
        /// or if the specified position is not within the current Content range.
        /// </summary>
        public static CodeFragment TryGetFragmentAt(this FileContentManager file, Position pos,
                                                    out CodeFragment.TokenIndex tIndex, bool includeEnd = false)
        {
            tIndex = null;
            if (file == null || pos == null || !Utils.IsValidPosition(pos, file))
            {
                return(null);
            }
            var start    = pos.Line;
            var previous = file.GetTokenizedLine(start).Where(token => token.GetRange().Start.Character <= pos.Character).ToImmutableArray();

            while (!previous.Any() && --start >= 0)
            {
                previous = file.GetTokenizedLine(start);
            }
            if (!previous.Any())
            {
                return(null);
            }

            var lastPreceding = previous.Last().WithUpdatedLineNumber(start);
            var overlaps      = includeEnd
                ? pos.IsSmallerThanOrEqualTo(lastPreceding.GetRange().End)
                : pos.IsSmallerThan(lastPreceding.GetRange().End);

            tIndex = overlaps ? new CodeFragment.TokenIndex(file, start, previous.Length - 1) : null;
            return(overlaps ? lastPreceding : null);
        }
Exemple #6
0
        /// <summary>
        /// Returns the CodeFragment at the given position if such a fragment exists and null otherwise.
        /// If the given position is equal to the end of the fragment, that fragment is returned if includeEnd is set to true.
        /// Returns null if the given file or the specified position is null,
        /// or if the specified position is not within the current Content range.
        /// </summary>
        public static CodeFragment TryGetFragmentAt(this FileContentManager file, Position pos, bool includeEnd = false)
        {
            if (file == null || pos == null || !Utils.IsValidPosition(pos, file))
            {
                return(null);
            }
            var start    = pos.Line;
            var previous = file.GetTokenizedLine(start).Where(token => token.GetRange().Start.IsSmallerThanOrEqualTo(new Position(0, pos.Character)));

            while (!previous.Any() && --start >= 0)
            {
                previous = file.GetTokenizedLine(start);
            }
            if (!previous.Any())
            {
                return(null);
            }

            var lastPreceding = previous.Last().WithUpdatedLineNumber(start);
            var overlaps      = includeEnd
                ? pos.IsSmallerThanOrEqualTo(lastPreceding.GetRange().End)
                : pos.IsSmallerThan(lastPreceding.GetRange().End);

            return(overlaps ? lastPreceding : null);
        }
        /// <summary>
        /// Returns the TokenIndex for the last token in the given file, or null if no such token exists.
        /// </summary>
        internal static CodeFragment.TokenIndex?LastToken(this FileContentManager file)
        {
            var lastNonEmpty = file.NrLines();

            while (lastNonEmpty-- > 0 && file.GetTokenizedLine(lastNonEmpty).Length == 0)
            {
            }
            return(lastNonEmpty < 0
                ? null
                : new CodeFragment.TokenIndex(file, lastNonEmpty, file.GetTokenizedLine(lastNonEmpty).Length - 1));
        }
Exemple #8
0
        /// <summary>
        /// Returns the TokenIndex for the last token in the given file, or null if no such token exists.
        /// Throws an ArgumentNullException if file is null.
        /// </summary>
        internal static CodeFragment.TokenIndex LastToken(this FileContentManager file)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            var lastNonEmpty = file.NrLines();

            while (lastNonEmpty-- > 0 && file.GetTokenizedLine(lastNonEmpty).Length == 0)
            {
            }
            return(lastNonEmpty < 0
                ? null
                : new CodeFragment.TokenIndex(file, lastNonEmpty, file.GetTokenizedLine(lastNonEmpty).Length - 1));
        }
        /// <summary>
        /// Returns the TokenIndex for the first token in the given file, or null if no such token exists.
        /// </summary>
        internal static CodeFragment.TokenIndex?FirstToken(this FileContentManager file)
        {
            var lineNr = 0;

            while (file.GetTokenizedLine(lineNr).Length == 0 && ++lineNr < file.NrLines())
            {
            }
            return(lineNr == file.NrLines()
                ? null
                : new CodeFragment.TokenIndex(file, lineNr, 0));
        }
        /// <summary>
        /// Given an SortedSet of changed lines, verifies the context for each token on one of these lines,
        /// and adds the computed diagnostics to the ones returned as out parameter.
        /// Marks the token indices which are to be excluded from compilation due to context errors.
        /// Returns the line numbers for which the context diagnostics have been recomputed.
        /// </summary>
        private static HashSet <int> VerifyContext(this FileContentManager file, SortedSet <int> changedLines, out List <Diagnostic> diagnostics)
        {
            IEnumerable <CodeFragment.TokenIndex> TokenIndices(int lineNr) =>
            Enumerable.Range(0, file.GetTokenizedLine(lineNr).Count()).Select(index => new CodeFragment.TokenIndex(file, lineNr, index));

            var tokensToVerify = changedLines.SelectMany(TokenIndices);
            var verifiedLines  = new HashSet <int>();

            List <Diagnostic> Verify(CodeFragment.TokenIndex tokenIndex)
            {
                var messages = new List <Diagnostic>();
                var fragment = tokenIndex.GetFragment();
                var context  = tokenIndex.GetContext();

                var(include, verifications) = Context.VerifySyntaxTokenContext(context);
                foreach (var msg in verifications)
                {
                    messages.Add(Diagnostics.Generate(file.FileName, msg, fragment.Range.Start));
                }

                if (include)
                {
                    tokenIndex.MarkAsIncluded();
                }
                else
                {
                    tokenIndex.MarkAsExcluded();
                }
                verifiedLines.Add(tokenIndex.Line);

                // if a token is newly included in or excluded from the compilation,
                // then this may impact context information for all following tokens
                var changedStatus = include ^ fragment.IncludeInCompilation;
                var next          = tokenIndex.NextOnScope();

                if (changedStatus && next != null && !changedLines.Contains(next.Line))
                {
                    // NOTE: since we invalidate context diagnostics on a per-line basis, we need to verify the whole line!
                    var tokens = TokenIndices(next.Line);
                    foreach (var token in tokens)
                    {
                        messages.AddRange(Verify(token));
                    }
                }
                return(messages);
            }

            diagnostics = tokensToVerify.SelectMany(tIndex => Verify(tIndex)).ToList();
            return(verifiedLines);
        }
Exemple #11
0
            /// <summary>
            /// Verifies the given line number and index *only* against the Tokens listed in file (and not against the content)
            /// and initializes an instance of TokenIndex.
            /// Throws an ArgumentNullException if file is null.
            /// Throws an ArgumentOutOfRangeException if line or index are negative,
            /// or line is larger than or equal to the number of Tokens lists in file,
            /// or index is larger than or equal to the number of Tokens on the given line.
            /// </summary>
            internal TokenIndex(FileContentManager file, int line, int index)
            {
                this.File = file ?? throw new ArgumentNullException(nameof(file));
                if (line < 0 || line >= file.NrTokenizedLines())
                {
                    throw new ArgumentOutOfRangeException(nameof(line));
                }
                if (index < 0 || index >= file.GetTokenizedLine(line).Length)
                {
                    throw new ArgumentOutOfRangeException(nameof(index));
                }

                this.Line  = line;
                this.Index = index;
            }
Exemple #12
0
        /// <summary>
        /// Returns the TokenIndex for the first token in the given file, or null if no such token exists.
        /// Throws an ArgumentNullException if file is null.
        /// </summary>
        internal static CodeFragment.TokenIndex FirstToken(this FileContentManager file)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            var lineNr = 0;

            while (file.GetTokenizedLine(lineNr).Length == 0 && ++lineNr < file.NrLines())
            {
            }
            return(lineNr == file.NrLines()
                ? null
                : new CodeFragment.TokenIndex(file, lineNr, 0));
        }
Exemple #13
0
            /// <summary>
            /// Verifies the given line number and index *only* against the Tokens listed in file (and not against the
            /// content) and initializes an instance of TokenIndex.
            /// </summary>
            /// <exception cref="ArgumentOutOfRangeException">The line or index are negative.</exception>
            /// <exception cref="FileContentException">
            /// The line is outside the bounds of the file, or the index is outside the bounds of the line.
            /// </exception>
            internal TokenIndex(FileContentManager file, int line, int index)
            {
                if (line < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(line));
                }
                if (index < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(index));
                }
                if (line >= file.NrTokenizedLines())
                {
                    throw new FileContentException("Line exceeds the bounds of the file.");
                }
                if (index >= file.GetTokenizedLine(line).Length)
                {
                    throw new FileContentException("Token exceeds the bounds of the line.");
                }

                this.file  = file;
                this.Line  = line;
                this.Index = index;
            }