OSDN Git Service

高速化のため読込部分をStringBufferに移動した
[fooeditengine/FooEditEngine.git] / Common / StringBuffer.cs
1 /*\r
2  * Copyright (C) 2013 FooProject\r
3  * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by\r
4  * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\r
5 \r
6  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of \r
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\r
8 \r
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.\r
10  */\r
11 using System;\r
12 using System.Collections.Generic;\r
13 using System.Globalization;\r
14 using System.Linq;\r
15 using System.Text;\r
16 using System.Threading;\r
17 using System.Threading.Tasks;\r
18 using Slusser.Collections.Generic;\r
19 \r
20 namespace FooEditEngine\r
21 {\r
22     /// <summary>\r
23     /// ランダムアクセス可能な列挙子を提供するインターフェイス\r
24     /// </summary>\r
25     /// <typeparam name="T"></typeparam>\r
26     public interface IRandomEnumrator<T>\r
27     {\r
28         /// <summary>\r
29         /// インデクサーを表す\r
30         /// </summary>\r
31         /// <param name="index">インデックス</param>\r
32         /// <returns>Tを返す</returns>\r
33         T this[int index]{get;}\r
34     }\r
35 \r
36     sealed class StringBuffer : IEnumerable<char>, IRandomEnumrator<char>\r
37     {\r
38         GapBuffer<char> buf = new GapBuffer<char>();\r
39 \r
40         public StringBuffer()\r
41         {\r
42             this.Update += (s, e) => { };\r
43         }\r
44 \r
45         public StringBuffer(StringBuffer buffer)\r
46             : this()\r
47         {\r
48             buf.AddRange(buffer.buf, buffer.Length);\r
49         }\r
50 \r
51         public char this[int index]\r
52         {\r
53             get\r
54             {\r
55                 char c = buf[index];\r
56                 return c;\r
57             }\r
58         }\r
59 \r
60         public string ToString(int index, int length)\r
61         {\r
62             StringBuilder temp = new StringBuilder();\r
63             temp.Clear();\r
64             for (int i = index; i < index + length; i++)\r
65                 temp.Append(buf[i]);\r
66             return temp.ToString();\r
67         }\r
68 \r
69         public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)\r
70         {\r
71             foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, maxCharCount))\r
72             {\r
73                 StringBuilder temp = new StringBuilder();\r
74                 temp.Clear();\r
75                 int lineEndIndex = range.Item1;\r
76                 if (range.Item2 > 0)\r
77                     lineEndIndex += range.Item2 - 1;\r
78                 for (int i = range.Item1; i <= lineEndIndex; i++)\r
79                     temp.Append(buf[i]);\r
80                 yield return temp.ToString();\r
81             }\r
82         }\r
83 \r
84         public IEnumerable<Tuple<int,int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)\r
85         {\r
86             int currentLineHeadIndex = startIndex;\r
87             int currentLineLength = 0;\r
88             \r
89             for (int i = startIndex; i <= endIndex; i++)\r
90             {\r
91                 currentLineLength++;\r
92                 char c = this.buf[i];\r
93                 if (c == Document.NewLine ||\r
94                     (maxCharCount != -1 && currentLineLength >= maxCharCount))\r
95                 {\r
96                     UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);\r
97                     if (uc != UnicodeCategory.NonSpacingMark &&\r
98                     uc != UnicodeCategory.SpacingCombiningMark &&\r
99                     uc != UnicodeCategory.EnclosingMark &&\r
100                     uc != UnicodeCategory.Surrogate)\r
101                     {\r
102                         yield return new Tuple<int,int>(currentLineHeadIndex, currentLineLength);\r
103                         currentLineHeadIndex += currentLineLength;\r
104                         currentLineLength = 0;\r
105                     }\r
106                 }\r
107             }\r
108             if (currentLineLength > 0)\r
109                 yield return new Tuple<int, int>(currentLineHeadIndex, currentLineLength);\r
110         }\r
111         \r
112         public int Length\r
113         {\r
114             get { return this.buf.Count; }\r
115         }\r
116 \r
117         internal event DocumentUpdateEventHandler Update;\r
118 \r
119         internal void Replace(StringBuffer buf)\r
120         {\r
121             this.Replace(buf.buf);\r
122         }\r
123 \r
124         internal void Replace(GapBuffer<char> buf)\r
125         {\r
126             this.Clear();\r
127             this.buf = buf;\r
128             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));\r
129         }\r
130 \r
131         internal void Replace(int index, int length, IEnumerable<char> chars,int count)\r
132         {\r
133             if (length > 0)\r
134                 this.buf.RemoveRange(index, length);\r
135             this.buf.InsertRange(index, chars,count);\r
136             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, index, length, count));\r
137         }\r
138 \r
139         internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)\r
140         {\r
141             string str;\r
142             for (int i = 0; (str = await fs.ReadLineAsync().ConfigureAwait(false)) != null; i++)\r
143             {\r
144                 int index = this.Length;\r
145                 if (index < 0)\r
146                     index = 0;\r
147 \r
148                 this.buf.InsertRange(index, str + Document.NewLine, str.Length + 1);\r
149 \r
150                 if (tokenSource != null)\r
151                     tokenSource.Token.ThrowIfCancellationRequested();\r
152 #if TEST_ASYNC\r
153                     System.Threading.Thread.Sleep(10);\r
154 #endif\r
155             }\r
156             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));\r
157             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));\r
158         }\r
159 \r
160         internal void Replace(string target, string pattern,bool ci = false)\r
161         {\r
162             TextSearch ts = new TextSearch(target,ci);\r
163             int left = 0, right = 0;\r
164             char[] pattern_chars = pattern.ToCharArray();\r
165             while((right = ts.IndexOf(this.buf,left)) != -1)\r
166             {\r
167                 this.buf.RemoveRange(right, target.Length);\r
168                 this.buf.InsertRange(right, pattern_chars, pattern.Length);\r
169                 left = right + pattern.Length;\r
170             }\r
171             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));\r
172             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));\r
173         }\r
174 \r
175         internal int IndexOf(string target, int start,bool ci = false)\r
176         {\r
177             TextSearch ts = new TextSearch(target,ci);\r
178             return ts.IndexOf(this.buf, start);\r
179         }\r
180 \r
181         /// <summary>\r
182         /// 文字列を削除する\r
183         /// </summary>\r
184         internal void Clear()\r
185         {\r
186             this.buf.Clear();\r
187             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, 0, this.buf.Count,0));\r
188         }\r
189 \r
190         internal IEnumerable<char> GetEnumerator(int start, int length)\r
191         {\r
192             for (int i = start; i < start + length; i++)\r
193                 yield return this.buf[i];\r
194         }\r
195 \r
196         #region IEnumerable<char> メンバー\r
197 \r
198         public IEnumerator<char> GetEnumerator()\r
199         {\r
200             for (int i = 0; i < this.Length; i++)\r
201                 yield return this.buf[i];\r
202         }\r
203 \r
204         #endregion\r
205 \r
206         #region IEnumerable メンバー\r
207 \r
208         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()\r
209         {\r
210             for (int i = 0; i < this.Length; i++)\r
211                 yield return this[i];\r
212         }\r
213 \r
214         #endregion\r
215     }\r
216 \r
217     sealed class TextSearch\r
218     {\r
219         char[] pattern;\r
220         int patternLength;\r
221         Dictionary<char, int> qsTable = new Dictionary<char, int>();\r
222         bool caseInsenstive;\r
223         public TextSearch(string pattern,bool ci = false)\r
224         {\r
225             this.patternLength = pattern.Length;\r
226             this.caseInsenstive = ci;\r
227             if (ci)\r
228             {\r
229                 this.CreateQSTable(pattern.ToLower());\r
230                 this.CreateQSTable(pattern.ToUpper());\r
231                 this.pattern = new char[pattern.Length];\r
232                 for (int i = 0; i < pattern.Length; i++)\r
233                     this.pattern[i] = CharTool.ToUpperFastIf(pattern[i]);\r
234             }\r
235             else\r
236             {\r
237                 this.CreateQSTable(pattern);\r
238                 this.pattern = pattern.ToCharArray();\r
239             }\r
240         }\r
241         void CreateQSTable(string pattern)\r
242         {\r
243             int len = pattern.Length;\r
244             for (int i = 0; i < len; i++)\r
245             {\r
246                 if (!this.qsTable.ContainsKey(pattern[i]))\r
247                     this.qsTable.Add(pattern[i], len - i);\r
248                 else\r
249                     this.qsTable[pattern[i]] = len - i;\r
250             }\r
251         }\r
252         public int IndexOf(GapBuffer<char> buf, int start)\r
253         {\r
254             //QuickSearch法\r
255             int buflen = buf.Count - 1;\r
256             int plen = this.patternLength;\r
257             int i = start;\r
258             int end = buf.Count - plen;\r
259             //最適化のためわざとコピペした\r
260             if (this.caseInsenstive)\r
261             {\r
262                 while (i <= end)\r
263                 {\r
264                     int j = 0;\r
265                     while (j < plen)\r
266                     {\r
267                         if (CharTool.ToUpperFastIf(buf[i + j]) != this.pattern[j])\r
268                             break;\r
269                         j++;\r
270                     }\r
271                     if (j == plen)\r
272                     {\r
273                         return i;\r
274                     }\r
275                     else\r
276                     {\r
277                         int k = i + plen;\r
278                         if (k <= buflen)        //buffer以降にアクセスする可能性がある\r
279                         {\r
280                             int moveDelta;\r
281                             if (this.qsTable.TryGetValue(buf[k], out moveDelta))\r
282                                 i += moveDelta;\r
283                             else\r
284                                 i += plen;\r
285                         }\r
286                         else\r
287                         {\r
288                             break;\r
289                         }\r
290                     }\r
291                 }\r
292 \r
293             }\r
294             else\r
295             {\r
296                 while (i <= end)\r
297                 {\r
298                     int j = 0;\r
299                     while (j < plen)\r
300                     {\r
301                         if (buf[i + j] != this.pattern[j])\r
302                             break;\r
303                         j++;\r
304                     }\r
305                     if (j == plen)\r
306                     {\r
307                         return i;\r
308                     }\r
309                     else\r
310                     {\r
311                         int k = i + plen;\r
312                         if (k <= buflen)        //buffer以降にアクセスする可能性がある\r
313                         {\r
314                             int moveDelta;\r
315                             if (this.qsTable.TryGetValue(buf[k], out moveDelta))\r
316                                 i += moveDelta;\r
317                             else\r
318                                 i += plen;\r
319                         }\r
320                         else\r
321                         {\r
322                             break;\r
323                         }\r
324                     }\r
325                 }\r
326             }\r
327             return -1;\r
328         }\r
329     }\r
330     static class CharTool\r
331     {\r
332         /// <summary>\r
333         /// Converts characters to lowercase.\r
334         /// </summary>\r
335         const string _lookupStringL =\r
336         "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[-]^_`abcdefghijklmnopqrstuvwxyz{|}~-";\r
337 \r
338         /// <summary>\r
339         /// Converts characters to uppercase.\r
340         /// </summary>\r
341         const string _lookupStringU =\r
342         "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[-]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~-";\r
343 \r
344         /// <summary>\r
345         /// Get lowercase version of this ASCII character.\r
346         /// </summary>\r
347         public static char ToLower(char c)\r
348         {\r
349             return _lookupStringL[c];\r
350         }\r
351 \r
352         /// <summary>\r
353         /// Get uppercase version of this ASCII character.\r
354         /// </summary>\r
355         public static char ToUpper(char c)\r
356         {\r
357             return _lookupStringU[c];\r
358         }\r
359 \r
360         /// <summary>\r
361         /// Translate uppercase ASCII characters to lowercase.\r
362         /// </summary>\r
363         public static char ToLowerFastIf(char c)\r
364         {\r
365             if (c >= 'A' && c <= 'Z')\r
366             {\r
367                 return (char)(c + 32);\r
368             }\r
369             else\r
370             {\r
371                 return c;\r
372             }\r
373         }\r
374 \r
375         /// <summary>\r
376         /// Translate lowercase ASCII characters to uppercase.\r
377         /// </summary>\r
378         public static char ToUpperFastIf(char c)\r
379         {\r
380             if (c >= 'a' && c <= 'z')\r
381             {\r
382                 return (char)(c - 32);\r
383             }\r
384             else\r
385             {\r
386                 return c;\r
387             }\r
388         }\r
389     }\r
390 }