2c3b66dc479c632ddcafdbed6ada4211540329b0
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / Microsoft.Tools.Common / Microsoft / Activities / Presentation / Xaml / SourceTextScanner.cs
1 // <copyright>
2 //   Copyright (c) Microsoft Corporation.  All rights reserved.
3 // </copyright>
4
5 namespace Microsoft.Activities.Presentation.Xaml
6 {
7     using System;
8     using System.Collections.Generic;
9     using System.Globalization;
10
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
14     {
15         private const char NewLine = '\n';
16
17         private const char Return = '\r';
18
19         private string source;
20
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;
25
26         internal SourceTextScanner(string source)
27         {
28             SharedFx.Assert(source != null, "source != null");
29             this.source = source;
30             this.indexCache = new List<Tuple<int, int>>();
31         }
32
33         internal Tuple<LineColumnPair, char> SearchCharAfter(LineColumnPair startPoint, params char[] charsToSearch)
34         {
35             SharedFx.Assert(startPoint != null, "startPoint != null");
36
37             int line = startPoint.LineNumber - 1;
38             int column = startPoint.ColumnNumber - 1;
39
40             HashSet<char> charsToSearchSet = new HashSet<char>(charsToSearch);
41             int index = this.GetIndex(line, column);
42             if (index < 0)
43             {
44                 return null;
45             }
46
47             bool firstLoop = true;
48             foreach (Tuple<char, int> currentPair in this.Scan(index))
49             {
50                 if (firstLoop)
51                 {
52                     firstLoop = false;
53                 }
54                 else
55                 {
56                     if (charsToSearchSet.Contains(currentPair.Item1))
57                     {
58                         LineColumnPair location = this.GetLocation(currentPair.Item2);
59                         SharedFx.Assert(location != null, "invalid location");
60                         return Tuple.Create(location, currentPair.Item1);
61                     }
62                 }
63             }
64
65             return null;
66         }
67
68         private LineColumnPair GetLocation(int index)
69         {
70             SharedFx.Assert(index >= 0 && index < this.source.Length, "index out of range");
71
72             while (!this.IsIndexInScannedLine(index))
73             {
74                 this.TryScanNextLine();
75             }
76             
77             int line = this.indexCache.Count - 1;
78             for (; line >= 0; --line)
79             {
80                 if (index >= this.indexCache[line].Item1)
81                 {
82                     break;
83                 }
84             }
85             
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");
89
90             return new LineColumnPair(line + 1, column + 1);
91         }
92         
93         private int GetIndex(int line, int column)
94         {
95             while (this.indexCache.Count <= line)
96             {
97                 if (!this.TryScanNextLine())
98                 {
99                     break;
100                 }
101             }
102
103             if (this.indexCache.Count <= line)
104             {
105                 SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "line out of range:({0},{1})", line + 1, column + 1));
106                 return -1;
107             }
108
109             if (column >= this.indexCache[line].Item2)
110             {
111                 SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "column out of range:({0},{1})", line + 1, column + 1));
112                 return -1;
113             }
114
115             return this.indexCache[line].Item1 + column;
116         }
117
118         private bool IsIndexInScannedLine(int index)
119         {
120             SharedFx.Assert(index >= 0 && index < this.source.Length, "invalid index");
121
122             int last = this.indexCache.Count - 1;
123             return last >= 0 && index < this.indexCache[last].Item1 + this.indexCache[last].Item2;
124         }
125
126         // return created
127         private bool TryScanNextLine()
128         {
129             int startIndex = 0;
130             if (this.indexCache.Count > 0)
131             {
132                 int tail = this.indexCache.Count - 1;
133                 startIndex = this.indexCache[tail].Item1 + this.indexCache[tail].Item2;
134             }
135
136             if (startIndex >= this.source.Length)
137             {
138                 return false;
139             }
140
141             int lastIndex = -1;
142             foreach (Tuple<char, int> currentPair in this.Scan(startIndex))
143             {
144                 lastIndex = currentPair.Item2;
145                 if (currentPair.Item1 == NewLine)
146                 {
147                     break;
148                 }
149             }
150
151             if (lastIndex < 0)
152             {
153                 SharedFx.Assert("lastIndex < 0");
154                 return false;
155             }
156
157             int lineLength = lastIndex - startIndex + 1;
158             this.indexCache.Add(Tuple.Create(startIndex, lineLength));
159             return true;
160         }
161
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)
167         {
168             if (index < 0 || index >= this.source.Length)
169             {
170                 SharedFx.Assert("index < 0 || index >= this.source.Length");
171                 yield break;
172             }
173
174             while (index < this.source.Length)
175             {
176                 char currentChar = this.source[index];
177
178                 if (currentChar == Return)
179                 {
180                     if (index + 1 < this.source.Length && this.source[index + 1] == NewLine)
181                     {
182                         ++index;
183                     }
184
185                     currentChar = NewLine;
186                 }
187
188                 yield return Tuple.Create(currentChar, index);
189                 ++index;
190             }
191         }
192     }
193 }