/// <summary> /// Dequeues all lines whose tokens has changed and verifies the positions of these tokens. /// Does nothing if no lines have been modified. /// Recomputes and pushes the context diagnostics for the processed tokens otherwise. /// </summary> internal static void UpdateContext(this FileContentManager file) { file.SyncRoot.EnterUpgradeableReadLock(); try { var changedLines = file.DequeueTokenChanges(); if (!changedLines.Any()) { return; } QsCompilerError.RaiseOnFailure( () => { var verifiedLines = file.VerifyContext(changedLines, out List <Diagnostic> diagnostics); file.UpdateContextDiagnostics(verifiedLines, diagnostics); }, "updating the ContextDiagnostics failed"); var edited = file.CallablesWithContentModifications(changedLines); file.MarkCallableAsContentEdited(edited); } finally { file.SyncRoot.ExitUpgradeableReadLock(); } }
} // will raise an exception if file is null // the actual update routine /// <summary> /// Attempts to perform the necessary updates when replacing the range [start, start + count) by newText for the given file /// wrapping each step in a QsCompilerError.RaiseOnFailure. /// </summary> private static void Update(this FileContentManager file, int start, int count, IEnumerable <string> newText) { CodeLine[] replacements = QsCompilerError.RaiseOnFailure(() => ComputeCodeLines(newText, start > 0 ? file.GetLine(start - 1) : null).ToArray(), "scope tracking update failed during computing the replacements"); IEnumerable <CodeLine> updateRemaining = QsCompilerError.RaiseOnFailure(() => ComputeUpdates(file, start, count, replacements), "scope tracking update failed during computing the updates"); QsCompilerError.RaiseOnFailure(() => { if (updateRemaining == null) { file.ContentUpdate(start, count, replacements); } else { file.ContentUpdate(start, file.NrLines() - start, replacements.Concat(updateRemaining).ToArray()); } }, "the proposed ContentUpdate failed"); QsCompilerError.RaiseOnFailure(() => { if (updateRemaining == null) { file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start, replacements.Length)); } else { file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start)); } file.AddScopeDiagnostics(file.CheckForMissingClosings()); }, "updating the scope diagnostics failed"); }
/// <summary> /// Assuming both the current tokens and the tokens to update are sorted according to their range, /// merges the current and updated tokens such that the merged collection is sorted as well. /// </summary> /// <exception cref="QsCompilerException">The token verification for the merged collection failed.</exception> internal static List <CodeFragment> MergeTokens(IEnumerable <CodeFragment> current, IEnumerable <CodeFragment> updated) { var merged = new List <CodeFragment>(0); void NextBatch(ref IEnumerable <CodeFragment> batch, IEnumerable <CodeFragment> next) { if (next.Any()) { var start = next.First().Range.Start; merged.AddRange(batch.TakeWhile(TokensUpTo(start))); batch = batch.SkipWhile(TokensUpTo(start)).ToList(); } else { merged.AddRange(batch); batch = Enumerable.Empty <CodeFragment>(); } } while (updated.Any() || current.Any()) { NextBatch(ref current, updated); NextBatch(ref updated, current); } var mergedTokens = merged.ToList(); QsCompilerError.RaiseOnFailure(() => VerifyTokenOrdering(mergedTokens), "merged tokens are not ordered"); return(mergedTokens); }
/// <summary> /// Dequeues all lines whose content has changed and extracts the code fragments overlapping with those lines that need to be reprocessed. /// Does nothing if no lines have been modified. /// Recomputes and pushes the syntax diagnostics for the extracted fragments and all end-of-file diagnostics otherwise. /// Processes the extracted fragment and inserts the processed fragments into the corresponding data structure /// Throws an ArgumentNullException if file is null. /// </summary> internal static void UpdateLanguageProcessing(this FileContentManager file) { if (file == null) { throw new ArgumentNullException(nameof(file)); } file.SyncRoot.EnterUpgradeableReadLock(); try { var changedLines = file.DequeueContentChanges(); if (!changedLines.Any()) { return; } var reprocess = QsCompilerError.RaiseOnFailure(() => file.FragmentsToProcess(changedLines).ToList(), "processing the edited lines failed"); var diagnostics = reprocess.CheckForEmptyFragments(file.FileName.Value) .Concat(ParseCode(ref reprocess, file.FileName.Value)).ToList(); QsCompilerError.RaiseOnFailure(() => file.TokensUpdate(reprocess), "the computed token update failed"); QsCompilerError.RaiseOnFailure(() => file.AddSyntaxDiagnostics(diagnostics), "updating the SyntaxDiagnostics failed"); } finally { file.SyncRoot.ExitUpgradeableReadLock(); } }
private Task ProcessingTaskAsync(Action action) => new Task(() => { try { QsCompilerError.RaiseOnFailure(action, this.exceptionHeader); } catch (Exception ex) { this.logException(ex); } });
/// <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> /// Verifies the given stringDelimiters and returns the given text without the content between the delimiters. /// </summary> private static string RemoveStrings(string text, IEnumerable <int> stringDelimiters) { QsCompilerError.RaiseOnFailure(() => VerifyStringDelimiters(text, stringDelimiters), "invalid delimiters for given text in call to RemoveStrings"); var iter = stringDelimiters.GetEnumerator(); var trimmed = iter.MoveNext() ? iter.Current < 0 ? string.Empty : text.Substring(0, StartDelimiter(iter.Current)) : text; while (iter.MoveNext() && iter.Current < text.Length) { // Note: if modifications here are needed, modify Start- and EndDelimiter to make sure these changes are reflected in IndexInFullString var end = iter.Current == text.Length ? text.Length : EndDelimiter(iter.Current); // end of a substring var start = iter.MoveNext() ? StartDelimiter(iter.Current) : text.Length; trimmed += text.Substring(end, start - end); } return(trimmed); }
// language server tools - // wrapping these into a try .. catch .. to make sure errors don't go unnoticed as they otherwise would public static T TryJTokenAs <T>(JToken arg) where T : class => QsCompilerError.RaiseOnFailure(() => arg.ToObject <T>(), "could not cast given JToken");