2 // Copyright (c) Microsoft Corporation. All rights reserved.
5 namespace Microsoft.Activities.Presentation.Xaml
8 using System.Collections.Generic;
9 using System.Globalization;
11 // NOTE: (x, y) denotes line x column y, where x and y are 0 based,
12 // while the line/column in SourceLocation and LineNumberPair is 1 based.
13 internal class SourceTextScanner
15 private const char NewLine = '\n';
17 private const char Return = '\r';
19 private string source;
21 // <start index, length>
22 // say a line is "abc\n"
23 // the length is 4, with '\n' included.
24 private List<Tuple<int, int>> indexCache;
26 internal SourceTextScanner(string source)
28 SharedFx.Assert(source != null, "source != null");
30 this.indexCache = new List<Tuple<int, int>>();
33 internal Tuple<LineColumnPair, char> SearchCharAfter(LineColumnPair startPoint, params char[] charsToSearch)
35 SharedFx.Assert(startPoint != null, "startPoint != null");
37 int line = startPoint.LineNumber - 1;
38 int column = startPoint.ColumnNumber - 1;
40 HashSet<char> charsToSearchSet = new HashSet<char>(charsToSearch);
41 int index = this.GetIndex(line, column);
47 bool firstLoop = true;
48 foreach (Tuple<char, int> currentPair in this.Scan(index))
56 if (charsToSearchSet.Contains(currentPair.Item1))
58 LineColumnPair location = this.GetLocation(currentPair.Item2);
59 SharedFx.Assert(location != null, "invalid location");
60 return Tuple.Create(location, currentPair.Item1);
68 private LineColumnPair GetLocation(int index)
70 SharedFx.Assert(index >= 0 && index < this.source.Length, "index out of range");
72 while (!this.IsIndexInScannedLine(index))
74 this.TryScanNextLine();
77 int line = this.indexCache.Count - 1;
78 for (; line >= 0; --line)
80 if (index >= this.indexCache[line].Item1)
86 SharedFx.Assert(line >= 0, "line < this.indexCache.Count");
87 int column = index - this.indexCache[line].Item1;
88 SharedFx.Assert(column < this.indexCache[line].Item2, "Should Not Happen");
90 return new LineColumnPair(line + 1, column + 1);
93 private int GetIndex(int line, int column)
95 while (this.indexCache.Count <= line)
97 if (!this.TryScanNextLine())
103 if (this.indexCache.Count <= line)
105 SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "line out of range:({0},{1})", line + 1, column + 1));
109 if (column >= this.indexCache[line].Item2)
111 SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "column out of range:({0},{1})", line + 1, column + 1));
115 return this.indexCache[line].Item1 + column;
118 private bool IsIndexInScannedLine(int index)
120 SharedFx.Assert(index >= 0 && index < this.source.Length, "invalid index");
122 int last = this.indexCache.Count - 1;
123 return last >= 0 && index < this.indexCache[last].Item1 + this.indexCache[last].Item2;
127 private bool TryScanNextLine()
130 if (this.indexCache.Count > 0)
132 int tail = this.indexCache.Count - 1;
133 startIndex = this.indexCache[tail].Item1 + this.indexCache[tail].Item2;
136 if (startIndex >= this.source.Length)
142 foreach (Tuple<char, int> currentPair in this.Scan(startIndex))
144 lastIndex = currentPair.Item2;
145 if (currentPair.Item1 == NewLine)
153 SharedFx.Assert("lastIndex < 0");
157 int lineLength = lastIndex - startIndex + 1;
158 this.indexCache.Add(Tuple.Create(startIndex, lineLength));
162 // Tuple<current charactor, charactor index>
163 // this Scan will replace \r\n=>\n \r=>\n
164 // \r\n return: <\n, \n's index>
165 // \r return: <\n, \r's index>
166 private IEnumerable<Tuple<char, int>> Scan(int index)
168 if (index < 0 || index >= this.source.Length)
170 SharedFx.Assert("index < 0 || index >= this.source.Length");
174 while (index < this.source.Length)
176 char currentChar = this.source[index];
178 if (currentChar == Return)
180 if (index + 1 < this.source.Length && this.source[index + 1] == NewLine)
185 currentChar = NewLine;
188 yield return Tuple.Create(currentChar, index);