185ec0840c30ccbf89f862caf21cce5fc8624b9a
[m17n/m17n-lib-cs.git] / MText.cs
1 using System;
2 using System.Text;
3 using System.Collections;
4 using System.Collections.Generic;
5 using M17N.Core;
6
7 namespace M17N.Core
8 {
9 #if false
10   public enum MTextFormat
11     {
12       MTEXT_FORMAT_US_ASCII,
13       MTEXT_FORMAT_UTF_8,
14       MTEXT_FORMAT_UTF_16BE,
15       MTEXT_FORMAT_UTF_16LE,
16       MTEXT_FORMAT_UTF_32BE,
17       MTEXT_FORMAT_UTF_32LE,
18     }
19 #endif
20
21   public class MTextProperty
22   {
23     internal MProperty prop;
24     internal bool front_sticky;
25     internal bool rear_sticky;
26     internal bool merginable;
27     public MProperty Prop { get { return prop; } }
28     public bool FrontSticky { get { return front_sticky; } }
29     public bool RearSticky { get { return rear_sticky; } }
30     public bool Merginable { get { return merginable; } }
31
32     public MTextProperty (bool front_sticky, bool rear_sticky)
33     {
34       this.front_sticky = front_sticky;
35       this.rear_sticky = rear_sticky;
36     }
37     public MTextProperty (bool front_sticky, bool rear_sticky, bool merginable)
38     {
39       this.front_sticky = front_sticky;
40       this.rear_sticky = rear_sticky;
41       this.merginable = merginable;
42     }
43   }
44
45   public class MText : IEnumerable, IComparable<MText>, IEquatable<MText>
46   {
47 #if false
48     public enum MTextFormat format;
49 #endif
50
51     private StringBuilder sb;
52     private int nchars;
53     private int cache_pos;
54     private int cache_idx;
55     private MInterval root_interval;
56     private bool read_only;
57
58     private static UTF8Encoding utf8 = new UTF8Encoding ();
59
60     private static int count_chars (String str)
61     {
62       int len = str.Length, n = 0;
63
64       for (int i = 0; i < len; i++, n++) 
65         if (surrogate_high_p (str[i]))
66           i++;
67       return n;
68     }
69
70     private static int count_chars (StringBuilder str)
71     {
72       int len = str.Length, n = 0;
73
74       for (int i = 0; i < len; i++, n++) 
75         if (surrogate_high_p (str[i]))
76           i++;
77       return n;
78     }
79
80     public MText ()
81       {
82         sb = new StringBuilder ();
83       }
84
85     public MText (byte[] str)
86       {
87         sb = new StringBuilder (utf8.GetString (str));
88         nchars = count_chars (sb);
89       }
90
91     public MText (String str)
92       {
93         sb = new StringBuilder (str);
94         nchars = count_chars (str);
95       }
96
97     public MText (StringBuilder str)
98       {
99         sb = str;
100         nchars = count_chars (str);
101       }
102
103     public static MText operator+ (MText mt1, MText mt2)
104     {
105       MText mt = new MText ();
106
107       mt.sb.Append (mt1.sb);
108       mt.sb.Append (mt2.sb);
109       mt.nchars = mt1.nchars + mt2.nchars;
110       return mt;
111     }
112
113     public bool ReadOnly { get { return read_only; } }
114     public int Length { get { return nchars; } }
115
116     public int CompareTo (MText other)
117     {
118       return this.sb.ToString ().CompareTo (other.sb.ToString ());
119     }
120
121     public bool Equals (MText other)
122     {
123       return this.sb.Equals (other.sb);
124     }
125
126     public override String ToString ()
127     {
128       return sb.ToString ();
129     }
130
131     private static bool surrogate_high_p (char c)
132     {
133       return (c >= 0xD800 && c < 0xDC00);
134     }
135
136     private static bool surrogate_low_p (char c)
137     {
138       return (c >= 0xDC00 && c < 0xE000);
139     }
140
141     private static int inc_idx (StringBuilder sb, int i)
142     {
143       return (i + (surrogate_high_p (sb[i]) ? 2 : 1));
144     }
145
146     private static int dec_idx (StringBuilder sb, int i)
147     {
148       return (i - (surrogate_low_p (sb[i - 1]) ? 2 : 1));
149     }
150
151     private static int pos_to_idx (MText mt, int pos)
152     {
153       if (pos == mt.cache_pos)
154         return mt.cache_idx;
155
156       int p, i;
157       bool forward;
158
159       if (pos < mt.cache_pos)
160         {
161           if (mt.cache_pos == mt.cache_idx)
162             return mt.cache_idx;
163           if (pos < mt.cache_pos - pos)
164             {
165               p = i = 0;
166               forward = true;
167             }
168           else
169             {
170               p = mt.cache_pos; i = mt.cache_idx;
171               forward = false;
172             }
173         }
174       else
175         {
176           if (mt.nchars - mt.cache_pos == mt.sb.Length - mt.cache_idx)
177             return (mt.cache_idx + pos - mt.cache_pos);
178           if (pos - mt.cache_pos < mt.nchars - pos)
179             {
180               p = mt.cache_pos; i = mt.cache_idx;
181               forward = true;
182             }
183           else
184             {
185               p = mt.nchars; i = mt.sb.Length;
186               forward = false;
187             }
188         }
189       if (forward)
190         for (; p < pos; i = inc_idx (mt.sb, i), p++);
191       else
192         for (; p > pos; i = dec_idx (mt.sb, i), p--);
193       mt.cache_pos = p;
194       mt.cache_idx = i;
195       return i;
196     }
197
198     private void insert (int pos, MText mt2, int from, int to)
199     {
200       int pos_idx = pos_to_idx (this, pos);
201       int from_idx = pos_to_idx (mt2, from);
202       int to_idx = pos_to_idx (mt2, to);
203
204       sb.Insert (pos_idx, mt2.sb.ToString (from_idx, to_idx - from_idx));
205       nchars += to - from;
206     }
207
208     public int this[int i]
209     {
210       set {
211         i = pos_to_idx (this, i);
212         if (value < 0x10000)
213           {
214             if (surrogate_high_p (sb[i]))
215               sb.Remove (i, 1);
216             sb[i] = (char) value;
217           }
218         else
219           {
220             char high = (char) (0xD800 + ((value - 0x10000) >> 10));
221             char low = (char) (0xDC00 + ((value - 0x10000) & 0x3FF));
222
223             if (! surrogate_high_p (sb[i]))
224               sb.Insert (i, 0);
225             sb[i] = high;
226             sb[i + 1] = low;
227           }
228       }
229       get {
230         i = pos_to_idx (this, i);
231         return (surrogate_high_p (sb[i])
232                 ? ((sb[i] - 0xD800) << 10) + (sb[i + 1] - 0xDC00) + 0x10000
233                 : sb[i]);
234       }
235     }
236
237     public MText dup ()
238     {
239       return (new MText (sb.ToString ()));
240     }
241
242     public MText ins (int pos, MText mt)
243     {
244       insert (pos, mt, 0, mt.nchars);
245       return this;
246     }
247
248     public MText ins (int pos, MText mt, int from, int to)
249     {
250       insert (pos, mt, from, to);
251       return this;
252     }
253
254     public MText del (int from, int to)
255     {
256       sb.Remove (from, pos_to_idx (this, to) - pos_to_idx (this, from));
257       nchars -= to - from;
258       return this;
259     }
260
261     public void PushProp (int from, int to, MTextProperty prop)
262     {
263       if (root_interval == null)
264         root_interval = new MInterval (0, true, sb.Length, true);
265       root_interval.Push (from, to, prop);
266     }
267
268     public IEnumerator GetEnumerator()
269     {
270       return new MTextEnum (this);
271     }
272
273     private class MInterval
274     {
275       // Start and end positions of this interval and its children.
276       // If this is the left node, the values are relative to the
277       // parent's total_start.  Otherwise, the values are relatie to
278       // the parent's total_end.
279       private int total_start, total_end;
280       // Stack of MTextProperty
281       private Stack<MTextProperty> stack;
282       private int depth;
283       private MInterval left, right, parent;
284
285       public MInterval (int start, bool front_inclusive,
286                         int end, bool rear_inclusive)
287       {
288         if (start > end)
289           throw new Exception ("Invalid Interval Range");
290         this.total_start = (start << 2) + (front_inclusive ? -1 : 1);
291         this.total_end = (end << 2) + (rear_inclusive ? 1 : -1);
292         this.stack = new Stack<MTextProperty> ();
293         this.depth = 1;
294       }
295
296       private MInterval (int start, int end, Stack<MTextProperty> stack)
297       {
298         this.total_start = start;
299         this.total_end = end;
300         this.stack = new Stack<MTextProperty> (stack);
301         this.depth = 1;
302       }
303
304       private MInterval Copy ()
305       {
306         return new MInterval (total_start, total_end, stack);
307       }
308
309       private int Start {
310         get {
311           return (left == null ? total_start : total_start + left.total_end);
312         }
313       }
314
315       private int End {
316         get {
317           return (right == null ? total_end : total_end + right.total_start);
318         }
319       }
320
321       private MInterval Left {
322         get {
323           MInterval interval;
324
325           if (left != null)
326             for (interval = left;
327                  interval.right != null;
328                  interval = interval.right);
329           else
330             for (interval = parent;
331                  interval != null && interval.total_start == 0;
332                  interval = interval.parent);
333
334           return interval;
335         }
336       }
337
338       private MInterval Right {
339         get {
340           MInterval interval;
341
342           if (right != null)
343             for (interval = right;
344                  interval.left != null;
345                  interval = interval.left);
346           else
347             for (interval = parent;
348                  interval != null && interval.total_start < 0;
349                  interval = interval.parent);
350
351           return interval;
352         }
353       }
354
355       public void Push (int start, int end, MTextProperty prop)
356       {
357         start <<= 2;
358         if (prop.FrontSticky)
359           start--;
360         else
361           start++;
362         end <<= 2;
363         if (prop.RearSticky)
364           end++;
365         else
366           end--;
367         if (start >= end)
368           throw new Exception ("Invalid Text Property Range");
369
370         push (start, end, prop);
371       }
372
373       private MInterval divide_right (int pos)
374       {
375         MInterval interval = Copy ();
376         int this_start = Start;
377         int this_end = End;
378
379         if (left == null || (right != null && left.depth < right.depth))
380           {
381             interval.left = this;
382             interval.right = right;
383             interval.parent = parent;
384             if (parent != null)
385               {
386                 if (total_start == 0)
387                   parent.left = interval;
388                 else
389                   parent.right = interval;
390               }
391             interval.depth = depth;
392             if (right == null)
393               interval.depth++;
394             total_start = 0;
395             total_end = pos - this_start;
396           }
397         else
398           {
399             interval.total_start = pos - this_end;
400             interval.total_end = 0;
401             if (right != null)
402               right.parent = interval;
403             right = interval;
404           }
405
406         return interval;
407       }
408
409       private MInterval divide_left (int pos)
410       {
411         MInterval interval = Copy ();
412         int this_start = Start;
413         int this_end = End;
414
415         if (left == null || (right != null && left.depth < right.depth))
416           {
417             interval.total_start = 0;
418             interval.total_end = pos - this_start;
419             if (left != null)
420               left.parent = interval;
421             left = interval;
422           }
423         else
424           {
425             interval.right = this;
426             interval.left = left;
427             interval.parent = parent;
428             if (parent != null)
429               {
430                 if (total_start == 0)
431                   parent.left = interval;
432                 else
433                   parent.right = interval;
434               }
435             interval.depth = depth;
436             if (left == null)
437               interval.depth++;
438             total_start = pos - this_end;
439             total_end = 0;
440           }
441
442         return interval;
443       }
444
445       private void push (int start, int end, MTextProperty prop)
446       {
447         int this_start = Start;
448         int this_end = End;
449
450         if (start < this_start)
451           {
452             if (end <= this_start)
453               {
454                 Left.push (start, end, prop);
455                 return;
456               }
457             Left.push (start, this_start, prop);
458             start = this_start;
459           }
460         if (this_end < end)
461           {
462             if (this_end < start)
463               {
464                 Right.push (start, end, prop);
465                 return;
466               }
467             Right.push (this_end, end, prop);
468             end = this_end;
469           }
470         if (this_start < start)
471           divide_left (start);
472         if (end < this_end)
473           divide_right (end);
474         stack.Push (prop);
475       }
476     }
477
478     private class MTextEnum : IEnumerator
479     {
480       private MText mt;
481       private int pos = -1;
482
483       public MTextEnum (MText mt)
484         {
485           this.mt = mt;
486         }
487
488       public bool MoveNext ()
489       {
490         pos++;
491         return (pos < mt.nchars);
492       }
493
494       public void Reset ()
495       {
496         pos = -1;
497       }
498
499       public object Current
500       {
501         get {
502           //if (pos < 0 || pos >= mt.nchars)
503           //throw new InvalidOperationException ();
504           return mt[pos];
505         }
506       }
507     }
508   }
509 }