public Location(string source, Position declOffset, QsLocation stmLoc, Range range) { this.SourceFile = source; this.DeclarationOffset = declOffset; this.RelativeStatementLocation = stmLoc; this.SymbolRange = range; }
/// <summary> /// Note that the only thing that may be set to null is the fragment kind - all other properties need to be set upon initialization /// </summary> private CodeFragment(int indent, Range range, string text, char next, QsComments?comments, QsFragmentKind?kind, bool include) { if (!DelimitingChars.Contains(next) && next != MissingDelimiter) { throw new ArgumentException("a CodeFragment needs to be followed by a DelimitingChar"); } this.Indentation = indent < 0 ? throw new ArgumentException("indentation needs to be positive") : indent; this.Text = text.TrimEnd(); this.FollowedBy = next; this.Comments = comments ?? QsComments.Empty; this.Kind = kind; // nothing here should be modifiable this.Range = range; this.HeaderRange = GetHeaderRange(this.Text, this.Kind); this.IncludeInCompilation = include; }
// external routines for context verification /// <summary> /// Given the line number of the lines that contain tokens that (possibly) have been modified, /// checks which callable declaration they can potentially belong to and returns the fully qualified name of those callables. /// </summary> internal static IEnumerable <(Range, QsQualifiedName)> CallablesWithContentModifications(this FileContentManager file, IEnumerable <int> changedLines) { var lastInFile = file.LastToken()?.GetFragment()?.Range?.End ?? file.End(); var callables = file.GetCallableDeclarations().Select(tuple => // these are sorted according to their line number { var ns = file.TryGetNamespaceAt(tuple.Item2.Start); QsCompilerError.Verify(ns != null, "namespace for callable declaration should not be null"); // invalid namespace names default to an unknown namespace name, but remain included in the compilation return(tuple.Item2.Start, new QsQualifiedName(ns, tuple.Item1)); }).ToList(); // NOTE: The range of modifications that has to trigger an update of the syntax tree for a callable // does need to go up to and include modifications to the line containing the next callable! // Otherwise inserting a callable declaration in the middle of an existing callable does not trigger the right behavior! (Range, QsQualifiedName) TypeCheckingRange((Position, QsQualifiedName) lastPreceding, IEnumerable <(Position, QsQualifiedName)> next) { var callableStart = lastPreceding.Item1; var callableEnd = next.Any() ? next.First().Item1 : lastInFile; return(Range.Create(callableStart, callableEnd), lastPreceding.Item2); } foreach (var lineNr in changedLines) { bool Precedes((Position, QsQualifiedName) tuple) => tuple.Item1.Line < lineNr; var preceding = callables.TakeWhile(Precedes); var following = callables.SkipWhile(Precedes); if (preceding.Any()) { yield return(TypeCheckingRange(preceding.Last(), following)); } if (following.Any() && following.First().Start.Line == lineNr) { yield return(TypeCheckingRange(following.First(), following.Skip(1))); } } }
internal CodeFragment SetRange(Range range) => new CodeFragment(this.Indentation, range, this.Text, this.FollowedBy, this.Comments, this.Kind, this.IncludeInCompilation);
internal CodeFragment(int indent, Range range, string text, char next, QsFragmentKind?kind = null) : this(indent, range, text, next, null, kind, true) { }
/// <summary> /// Returns a function that returns true if a given fragment does not overlap with the specified range. /// </summary> internal static Func <CodeFragment, bool> NotOverlappingWith(Range relRange) => token => token.IsWithinRange(Range.Create(Position.Zero, relRange.Start)) || TokensAfter(relRange.End)(token);
/// <summary> /// Returns true if the given token is fully included in the given range. /// </summary> internal static bool IsWithinRange(this CodeFragment token, Range range) => range.Contains(token.Range.Start) && range.ContainsEnd(token.Range.End);
/// <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); }
/// <summary> /// Given the location information for a declared symbol, /// as well as the position of the declaration within which the symbol is declared, /// returns the zero-based line and character index indicating the position of the symbol in the file. /// Returns null if the given object is not compatible with the position information generated by this CompilationBuilder. /// </summary> public static Position SymbolPosition(QsLocation rootLocation, QsNullable <Position> symbolPosition, Range symbolRange) { // the position offset is set to null (only) for variables defined in the declaration var offset = symbolPosition.IsNull ? rootLocation.Offset : rootLocation.Offset + symbolPosition.Item; return(offset + symbolRange.Start); }
/// <summary> /// Returns true if the start position of the diagnostic range is contained in the given range, excluding the /// given range's end position. /// </summary> internal static bool SelectByStart(this Diagnostic m, Range range) => !(m?.Range?.Start is null) && range.Contains(m.Range.Start.ToQSharp());
// utils for getting the necessary information for editor commands internal static Location AsLocation(string source, Position offset, Range relRange) =>
/// <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, Range range) { if (file is null || range is null) { return(Enumerable.Empty <CodeFragment>()); } var(start, end) = (range.Start.Line, range.End.Line); var fragAtStart = file.TryGetFragmentAt(range.Start, out _, includeEnd: true); var inRange = file.GetTokenizedLine(start).Select(t => t.WithLineNumOffset(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.WithLineNumOffset(start + 1 + i)))) .Concat(file.GetTokenizedLine(end).Select(t => t.WithLineNumOffset(end)).Where(ContextBuilder.TokensStartingBefore(range.End))); var fragments = ImmutableArray.CreateBuilder <CodeFragment>(); if (fragAtStart != null) { fragments.Add(fragAtStart); } fragments.AddRange(inRange); return(fragments.ToImmutableArray()); }