*** empty log message ***
[m17n/m17n-lib-cs.git] / MText.cs
index 477d7e6..de090a9 100644 (file)
--- a/MText.cs
+++ b/MText.cs
@@ -1,5 +1,7 @@
 using System;
 using System.Text;
+using System.Collections;
+using System.Collections.Generic;
 using M17N.Core;
 
 namespace M17N.Core
@@ -16,68 +18,383 @@ namespace M17N.Core
   }
 #endif
 
+  public class MTextProperty
+  {
+    internal MProperty prop;
+    internal bool front_sticky;
+    internal bool rear_sticky;
+    internal bool merginable;
+    public MProperty Prop { get { return prop; } }
+    public bool FrontSticky { get { return front_sticky; } }
+    public bool RearSticky { get { return rear_sticky; } }
+    public bool Merginable { get { return merginable; } }
+
+    public MTextProperty (bool front_sticky, bool rear_sticky)
+    {
+      this.front_sticky = front_sticky;
+      this.rear_sticky = rear_sticky;
+    }
+    public MTextProperty (bool front_sticky, bool rear_sticky, bool merginable)
+    {
+      this.front_sticky = front_sticky;
+      this.rear_sticky = rear_sticky;
+      this.merginable = merginable;
+    }
+  }
+
   public class MText
   {
 #if false
     public enum MTextFormat format;
 #endif
 
-    private class MTextPlist : MPlist
-    {
-      public class MInterval
-      {
-       MPlist stack;
-       int nprops;
-       public int start, end;
-       public MInterval prev, next;
-      }
-
-      MInterval head, tail;
-
-      public MTextPlist (MText mt)
-      {
-       head = tail = new MInterval ();
-       head.start = 0;
-       head.end = mt.sb.Length;
-      }
-    }
-
     private StringBuilder sb;
+    private int nchars;
     private int cache_pos;
     private int cache_idx;
-    private MTextPlist plist;
+    private MInterval root_interval;
+    private bool unmodifiable;
 
     private static UTF8Encoding utf8 = new UTF8Encoding ();
 
+    private static int count_chars (String str)
+    {
+      int len = str.Length, n = 0;
+
+      for (int i = 0; i < len; i++) 
+       n += surrogate_high_p (str[i]) ? 2 : 1;
+      return n;
+    }
+
+    private static int count_chars (StringBuilder str)
+    {
+      int len = str.Length, n = 0;
+
+      for (int i = 0; i < len; i++) 
+       n += surrogate_high_p (str[i]) ? 2 : 1;
+      return n;
+    }
+
     public MText ()
     {
-      cache_pos = cache_idx = 0;
-      plist = null;
       sb = new StringBuilder ();
     }
 
     public MText (byte[] str)
     {
-      cache_pos = cache_idx = 0;
-      plist = null;          
       sb = new StringBuilder (utf8.GetString (str));
+      nchars = count_chars (sb);
     }
 
     public MText (String str)
     {
-      cache_pos = cache_idx = 0;
-      plist = null;          
       sb = new StringBuilder (str);
+      nchars = count_chars (str);
+    }
+
+    public MText (StringBuilder str)
+    {
+      sb = str;
+      nchars = count_chars (str);
+    }
+
+    public static MText operator+ (MText mt1, MText mt2)
+    {
+      MText mt = new MText (mt1.sb);
+
+      mt.sb.Append (mt2.sb);
+      mt.nchars = mt1.nchars + mt2.nchars;
+      return mt;
+    }
+
+    public override string ToString ()
+    {
+      return sb.ToString ();
+    }
+
+    private static bool surrogate_high_p (char c)
+    {
+      return (c >= 0xD800 && c < 0xDC00);
+    }
+
+    private static bool surrogate_low_p (char c)
+    {
+      return (c >= 0xDC00 && c < 0xE000);
+    }
+
+    private static int inc_idx (StringBuilder sb, int i)
+    {
+      return (i + (surrogate_high_p (sb[i]) ? 2 : 1));
+    }
+
+    private static int dec_idx (StringBuilder sb, int i)
+    {
+      return (i - (surrogate_low_p (sb[i - 1]) ? 2 : 1));
+    }
+
+    private static int pos_to_idx (MText mt, int pos)
+    {
+      if (pos == mt.cache_pos)
+       return mt.cache_idx;
+
+      int p, i;
+      bool forward;
+
+      if (pos < mt.cache_pos)
+       {
+         if (mt.cache_pos == mt.cache_idx)
+           return mt.cache_idx;
+         if (pos < mt.cache_pos - pos)
+           {
+             p = i = 0;
+             forward = true;
+           }
+         else
+           {
+             p = mt.cache_pos; i = mt.cache_idx;
+             forward = false;
+           }
+       }
+      else
+       {
+         if (mt.nchars - mt.cache_pos == mt.sb.Length - mt.cache_idx)
+           return (mt.cache_idx + pos - mt.cache_pos);
+         if (pos - mt.cache_pos < mt.nchars - pos)
+           {
+             p = mt.cache_pos; i = mt.cache_idx;
+             forward = true;
+           }
+         else
+           {
+             p = mt.nchars; i = mt.sb.Length;
+             forward = false;
+           }
+       }
+      if (forward)
+       for (; p < pos; i = inc_idx (mt.sb, i), p++);
+      else
+       for (; p > pos; i = dec_idx (mt.sb, i), p--);
+      mt.cache_pos = p;
+      mt.cache_idx = i;
+      return i;
+    }
+
+    private void insert (int pos, MText mt2, int from, int to)
+    {
+      int pos_idx = pos_to_idx (this, pos);
+      int from_idx = pos_to_idx (mt2, from);
+      int to_idx = pos_to_idx (mt2, to);
+
+      sb.Insert (pos_idx, mt2.sb.ToString (from_idx, to_idx - from_idx));
+      nchars += to - from;
     }
 
     public int this[int i]
     {
-      set { this.sb[i] = (char) value; }
-      get { return this.sb[i]; }
+      set {
+       i = pos_to_idx (this, i);
+       if (value < 0x10000)
+         {
+           if (surrogate_high_p (sb[i]))
+             sb.Remove (i, 1);
+           sb[i] = (char) value;
+         }
+       else
+         {
+           char high = (char) (0xD800 + ((value - 0x10000) >> 10));
+           char low = (char) (0xDC00 + ((value - 0x10000) & 0x3FF));
+
+           if (! surrogate_high_p (sb[i]))
+             sb.Insert (i, 0);
+           sb[i] = high;
+           sb[i + 1] = low;
+         }
+      }
+      get {
+       i = pos_to_idx (this, i);
+       return (surrogate_high_p (sb[i])
+               ? ((sb[i] - 0xD800) << 10) + (sb[i + 1] - 0xDC00) + 0x10000
+               : sb[i]);
+      }
     }
-  }
 
-  public class MTextProperty
-  {
+    public MText dup ()
+    {
+      return (new MText (sb.ToString ()));
+    }
+
+    public MText ins (int pos, MText mt)
+    {
+      insert (pos, mt, 0, mt.nchars);
+      return this;
+    }
+
+    public MText ins (int pos, MText mt, int from, int to)
+    {
+      insert (pos, mt, from, to);
+      return this;
+    }
+
+    public MText del (int from, int to)
+    {
+      sb.Remove (from, pos_to_idx (this, to) - pos_to_idx (this, from));
+      nchars -= to - from;
+      return this;
+    }
+
+    private class MInterval
+    {
+      // Start and end positions of the MText covered of this interval
+      // and its children.  The values are actually (4N +- 1), where N
+      // is a non-negative integer representing a position relative to
+      // the parent interval.
+      private int total_start, total_end;
+      // Stack of MTextProperty
+      private Stack<MTextProperty> stack;
+      // Number of child nodes
+      private int nodes;
+      private MInterval left, right, parent;
+
+      private int Start {
+       get {
+         return (left == null ? total_start : total_start + left.total_end);
+       }
+      }
+
+      private int End {
+       get {
+         return (right == null ? total_end : total_start + right.total_start);
+       }
+      }
+
+      private MInterval Left {
+       get {
+         MInterval interval;
+
+         if (left != null)
+           {
+             for (interval = left; interval.right != null;
+                  interval = interval.right);
+           }
+         else
+           {
+             for (interval = parent;
+                  interval != null && interval.total_start == total_start;
+                  interval = interval.parent);
+           }
+         return interval;
+       }
+      }
+
+      private MInterval Right {
+       get {
+         MInterval interval;
+
+         if (right != null)
+           {
+             for (interval = right; interval.left != null;
+                  interval = interval.left);
+           }
+         else
+           {
+             for (interval = parent;
+                  interval != null && interval.total_end == total_end;
+                  interval = interval.parent);
+           }
+         return interval;
+       }
+      }
+
+      private static int MakePosition (int pos, bool front_inclusive)
+      {
+       return (pos << 2) + (front_inclusive ? -1 : 1);
+      }
+
+      private MInterval (int start, int end)
+      {
+       if (start > end)
+         throw new Exception ("Invalid Interval Range");
+       this.total_start = (start << 2) + 1;
+       this.total_end = (end << 2) + -1;
+       this.stack = new Stack<MTextProperty> ();
+       this.nodes = 1;
+      }
+
+      public MInterval (int start, bool front_inclusive,
+                       int end, bool rear_inclusive)
+      {
+       if (start > end)
+         throw new Exception ("Invalid Interval Range");
+       this.total_start = (start << 2) + (front_inclusive ? -1 : 1);
+       this.total_end = (end << 2) + (rear_inclusive ? 1 : -1);
+       this.stack = new Stack<MTextProperty> ();
+       this.nodes = 1;
+      }
+
+      public void Push (MTextProperty prop, int start, int end)
+      {
+       start <<= 2;
+       if (prop.FrontSticky)
+         start--;
+       else
+         start++;
+       end <<= 2;
+       if (prop.RearSticky)
+         end++;
+       else
+         end--;
+       if (start >= end)
+         throw new Exception ("Invalid Text Property Range");
+
+       push (prop, start, end);
+      }
+
+      private MInterval divide_right (int pos)
+      {
+       MInterval interval = new MInterval (pos, End);
+
+       return interval;
+      }
+
+      private MInterval divide_left (int pos)
+      {
+       MInterval interval = new MInterval (Start, pos);
+
+       return interval;
+      }
+
+      private void push (MTextProperty prop, int start, int end)
+      {
+       int this_start = Start;
+       int this_end = End;
+
+       if (start < this_start)
+         {
+           if (end <= this_start)
+             Left.push (prop, start, end);
+           else
+             {
+               Left.push (prop, start, this_start);
+               if (end < this_end)
+                 {
+                   divide_right (end);
+                   stack.Push (prop);
+                 }
+               else
+                 {
+                   stack.Push (prop);
+                   if (this_end < end)
+                     Right.Push (prop, this_end, end);
+                 }
+             }
+         }
+       else if (start < this_end)
+         {
+           divide_right (start).Push (prop, start, end);
+         }
+       else
+         Right.Push (prop, start, end);
+      }
+    }
   }
 }