/// <summary> /// Returns the index in the fragment text corresponding to the given absolute position. /// </summary> /// <exception cref="ArgumentException">Thrown when the position is outside the fragment range.</exception> /// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception> private static int GetTextIndexFromPosition(CodeFragment fragment, Position position) { if (fragment == null) { throw new ArgumentNullException(nameof(fragment)); } if (position == null) { throw new ArgumentNullException(nameof(position)); } var relativeLine = position.Line - fragment.GetRange().Start.Line; var lines = Utils.SplitLines(fragment.Text).DefaultIfEmpty(""); var relativeCharacter = relativeLine == 0 ? position.Character - fragment.GetRange().Start.Character : position.Character; if (relativeLine < 0 || relativeLine >= lines.Count() || relativeCharacter < 0 || relativeCharacter > lines.ElementAt(relativeLine).Length) { throw new ArgumentException("Position is outside the fragment range", nameof(position)); } return(lines.Take(relativeLine).Sum(line => line.Length) + relativeCharacter); }
/// <summary> /// Calls the Q# parser on each fragment, splitting one fragment into several if necessary /// (i.e. modifies the list of given fragments!). /// Fragments for which the code only consists of whitespace are left unchanged (i.e. the Kind remains set to null). /// Adds a suitable error to the returned diagnostics for each fragment that cannot be processed. /// Raises an ArgumentNullException if the given diagnostics or fragments are null. /// </summary> private static IEnumerable <Diagnostic> ParseCode(ref List <CodeFragment> fragments, string filename) { if (fragments == null) { throw new ArgumentNullException(nameof(fragments)); } var processedFragments = new List <CodeFragment>(fragments.Count()); var diagnostics = new List <Diagnostic>(); foreach (var snippet in fragments) { var snippetStart = snippet.GetRange().Start; var outputs = Parsing.ProcessCodeFragment(snippet.Text); for (var outputIndex = 0; outputIndex < outputs.Length; ++outputIndex) { var output = outputs[outputIndex]; var fragmentRange = DiagnosticTools.GetAbsoluteRange(snippetStart, output.Range); var fragment = new CodeFragment( snippet.Indentation, fragmentRange, output.Text.Value, outputIndex == outputs.Length - 1 ? snippet.FollowedBy : CodeFragment.MissingDelimiter, output.Kind); processedFragments.Add(fragment); var checkEnding = true; // if there is already a diagnostic overlapping with the ending, then don't bother checking the ending foreach (var fragmentDiagnostic in output.Diagnostics) { var generated = Diagnostics.Generate(filename, fragmentDiagnostic, fragmentRange.Start); diagnostics.Add(generated); var fragmentEnd = fragment.GetRange().End; var diagnosticGoesUpToFragmentEnd = fragmentEnd.IsWithinRange(generated.Range) || fragmentEnd.Equals(generated.Range.End); if (fragmentDiagnostic.Diagnostic.IsError && diagnosticGoesUpToFragmentEnd) { checkEnding = false; } } if (checkEnding) { diagnostics.AddRange(fragment.CheckFragmentDelimiters(filename)); } } if (outputs.Length == 0) { processedFragments.Add(snippet); // keep empty fragments around (note that the kind is set to null in this case!) } } QsCompilerError.RaiseOnFailure(() => ContextBuilder.VerifyTokenOrdering(processedFragments), "processed fragments are not ordered properly and/or overlap"); fragments = processedFragments; return(diagnostics); }
/// <summary> /// Compares the saved fragment ending of the given fragment against the expected continuation and /// adds the corresponding error to the returned diagnostics if they don't match. /// Throws an ArgumentException if the code fragment kind of the given fragment is unspecified (i.e. null). /// Throws an ArgumentNullException if the diagnostics are null. /// </summary> private static IEnumerable <Diagnostic> CheckFragmentDelimiters(this CodeFragment fragment, string filename) { if (fragment?.Kind == null) { throw new ArgumentException("missing specification of the fragment kind"); } var code = fragment.Kind.InvalidEnding; if (Diagnostics.ExpectedEnding(code) != fragment.FollowedBy) { yield return(Errors.InvalidFragmentEnding(filename, code, fragment.GetRange().End)); } }
/// <summary> /// Returns true if the given token is fully included in the given range. /// Throws an ArgumentNullException if token or the range delimiters are null. /// Throws an ArgumentException if the given range is not valid. /// </summary> internal static bool IsWithinRange(this CodeFragment token, Range range) { if (token == null) { throw new ArgumentNullException(nameof(token)); } if (!Utils.IsValidRange(range)) { throw new ArgumentException("invalid range"); } var tokenRange = token.GetRange(); return(tokenRange.Start.IsWithinRange(range) && tokenRange.End.IsWithinRange(range, includeEnd: true)); }
/// <summary> /// Returns a substring of the fragment text before the given position. /// <para/> /// If the fragment is null or the position is after the fragment's delimiter, returns the empty string. If the /// position is after the end of the fragment text but before the delimiter, the entire text is returned with a /// space character appended to it. /// </summary> /// <exception cref="ArgumentNullException">Thrown when file or position is null.</exception> /// <exception cref="ArgumentException">Thrown when the position is invalid.</exception> private static string GetFragmentTextBeforePosition( FileContentManager file, CodeFragment fragment, Position position) { if (file == null) { throw new ArgumentNullException(nameof(file)); } if (!Utils.IsValidPosition(position)) { throw new ArgumentException(nameof(position)); } if (fragment == null || IsPositionAfterDelimiter(file, fragment, position)) { return(""); } return(fragment.GetRange().End.IsSmallerThan(position) ? fragment.Text + " " : fragment.Text.Substring(0, GetTextIndexFromPosition(fragment, position))); }
/// <summary> /// Returns the position of the delimiting character that follows the given code fragment. /// </summary> /// <exception cref="ArgumentException">Thrown when the code fragment has a missing delimiter.</exception> /// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception> private static Position GetDelimiterPosition(FileContentManager file, CodeFragment fragment) { if (file == null) { throw new ArgumentNullException(nameof(file)); } if (fragment == null) { throw new ArgumentNullException(nameof(fragment)); } if (fragment.FollowedBy == CodeFragment.MissingDelimiter) { throw new ArgumentException("Code fragment has a missing delimiter", nameof(fragment)); } var end = fragment.GetRange().End; var position = file.FragmentEnd(ref end); return(new Position(position.Line, position.Character - 1)); }
/// <summary> /// Comparing for equality by value, /// returns the index of the first element in the given list of CodeFragments that matches the given token, /// or -1 if no such element exists. /// Throws an ArgumentNullException if the given list is null. /// </summary> internal static int FindByValue(this IReadOnlyList <CodeFragment> list, CodeFragment token) { if (list == null) { throw new ArgumentNullException(nameof(list)); } if (token == null) { var nrNonNull = list.TakeWhile(x => x != null).Count(); return(nrNonNull == list.Count ? -1 : nrNonNull); } var index = -1; var tokenRange = token.GetRange(); while (++index < list.Count && list[index].GetRange().Start.IsSmallerThan(tokenRange.Start)) { } return(index < list.Count && list[index].Equals(token) ? index : -1); }