// // Copyright (c) Microsoft Corporation. All rights reserved. // namespace Microsoft.Activities.Presentation.Xaml { using System; using System.Collections.Generic; using System.Globalization; // NOTE: (x, y) denotes line x column y, where x and y are 0 based, // while the line/column in SourceLocation and LineNumberPair is 1 based. internal class SourceTextScanner { private const char NewLine = '\n'; private const char Return = '\r'; private string source; // // say a line is "abc\n" // the length is 4, with '\n' included. private List> indexCache; internal SourceTextScanner(string source) { SharedFx.Assert(source != null, "source != null"); this.source = source; this.indexCache = new List>(); } internal Tuple SearchCharAfter(LineColumnPair startPoint, params char[] charsToSearch) { SharedFx.Assert(startPoint != null, "startPoint != null"); int line = startPoint.LineNumber - 1; int column = startPoint.ColumnNumber - 1; HashSet charsToSearchSet = new HashSet(charsToSearch); int index = this.GetIndex(line, column); if (index < 0) { return null; } bool firstLoop = true; foreach (Tuple currentPair in this.Scan(index)) { if (firstLoop) { firstLoop = false; } else { if (charsToSearchSet.Contains(currentPair.Item1)) { LineColumnPair location = this.GetLocation(currentPair.Item2); SharedFx.Assert(location != null, "invalid location"); return Tuple.Create(location, currentPair.Item1); } } } return null; } private LineColumnPair GetLocation(int index) { SharedFx.Assert(index >= 0 && index < this.source.Length, "index out of range"); while (!this.IsIndexInScannedLine(index)) { this.TryScanNextLine(); } int line = this.indexCache.Count - 1; for (; line >= 0; --line) { if (index >= this.indexCache[line].Item1) { break; } } SharedFx.Assert(line >= 0, "line < this.indexCache.Count"); int column = index - this.indexCache[line].Item1; SharedFx.Assert(column < this.indexCache[line].Item2, "Should Not Happen"); return new LineColumnPair(line + 1, column + 1); } private int GetIndex(int line, int column) { while (this.indexCache.Count <= line) { if (!this.TryScanNextLine()) { break; } } if (this.indexCache.Count <= line) { SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "line out of range:({0},{1})", line + 1, column + 1)); return -1; } if (column >= this.indexCache[line].Item2) { SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "column out of range:({0},{1})", line + 1, column + 1)); return -1; } return this.indexCache[line].Item1 + column; } private bool IsIndexInScannedLine(int index) { SharedFx.Assert(index >= 0 && index < this.source.Length, "invalid index"); int last = this.indexCache.Count - 1; return last >= 0 && index < this.indexCache[last].Item1 + this.indexCache[last].Item2; } // return created private bool TryScanNextLine() { int startIndex = 0; if (this.indexCache.Count > 0) { int tail = this.indexCache.Count - 1; startIndex = this.indexCache[tail].Item1 + this.indexCache[tail].Item2; } if (startIndex >= this.source.Length) { return false; } int lastIndex = -1; foreach (Tuple currentPair in this.Scan(startIndex)) { lastIndex = currentPair.Item2; if (currentPair.Item1 == NewLine) { break; } } if (lastIndex < 0) { SharedFx.Assert("lastIndex < 0"); return false; } int lineLength = lastIndex - startIndex + 1; this.indexCache.Add(Tuple.Create(startIndex, lineLength)); return true; } // Tuple // this Scan will replace \r\n=>\n \r=>\n // \r\n return: <\n, \n's index> // \r return: <\n, \r's index> private IEnumerable> Scan(int index) { if (index < 0 || index >= this.source.Length) { SharedFx.Assert("index < 0 || index >= this.source.Length"); yield break; } while (index < this.source.Length) { char currentChar = this.source[index]; if (currentChar == Return) { if (index + 1 < this.source.Length && this.source[index + 1] == NewLine) { ++index; } currentChar = NewLine; } yield return Tuple.Create(currentChar, index); ++index; } } } }