* roottypes.cs: Rename from tree.cs.
[mono.git] / mcs / class / System / System.Text.RegularExpressions / replace.cs
index 46492bec2838a49a2e5ad4a4c321c245d91daaba..7b8a56b279f5c4275fd1fa53bc44544574035fe9 100644 (file)
@@ -1,10 +1,10 @@
-//\r
-// assembly:   System\r
-// namespace:  System.Text.RegularExpressions\r
-// file:       replace.cs\r
-//\r
-// author:     Dan Lewis (dlewis@gmx.co.uk)\r
-//             (c) 2002\r
+//
+// assembly:   System
+// namespace:  System.Text.RegularExpressions
+// file:       replace.cs
+//
+// author:     Dan Lewis (dlewis@gmx.co.uk)
+//             (c) 2002
 
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
-\r
-using System;\r
-using System.Text;\r
-using System.Collections;\r
-\r
-using Parser = System.Text.RegularExpressions.Syntax.Parser;\r
-\r
-namespace System.Text.RegularExpressions {\r
-\r
-       class ReplacementEvaluator {\r
-               public static string Evaluate (string replacement, Match match) {\r
-                       ReplacementEvaluator ev = new ReplacementEvaluator (match.Regex, replacement);\r
-                       return ev.Evaluate (match);\r
-               }\r
-\r
-               public ReplacementEvaluator (Regex regex, string replacement) {\r
-                       this.regex = regex;\r
-                       terms = new ArrayList ();\r
-                       Compile (replacement);\r
-               }\r
-\r
-               public string Evaluate (Match match) {\r
-                       StringBuilder result = new StringBuilder ();\r
-                       foreach (Term term in terms)\r
-                               result.Append (term.GetResult (match));\r
-\r
-                       return result.ToString ();\r
-               }\r
-\r
-               // private\r
-\r
-               private void Compile (string replacement) {\r
-                       replacement = Parser.Unescape (replacement);\r
-                       string literal = "";\r
-\r
-                       int ptr = 0;\r
-                       char c;\r
-                       Term term = null;\r
-                       while (ptr < replacement.Length) {\r
-                               c = replacement[ptr ++];\r
-\r
-                               if (c == '$') {\r
-                                       if (replacement[ptr] != '$') \r
-                                               term = CompileTerm (replacement, ref ptr);\r
-                                       else\r
-                                               ++ ptr;\r
-                               }\r
-\r
-                               if (term != null) {\r
-                                       term.Literal = literal;\r
-                                       terms.Add (term);\r
-\r
-                                       term = null;\r
-                                       literal = "";\r
-                               }\r
-                               else\r
-                                       literal += c;\r
-                       }\r
-\r
-                       if (term == null && literal.Length > 0) {\r
-                               terms.Add (new Term (literal));\r
-                       }\r
-               }\r
-\r
-               private Term CompileTerm (string str, ref int ptr) {\r
-                       char c = str[ptr];\r
-\r
-                       if (Char.IsDigit (c)) {         // numbered group\r
-                               int n = Parser.ParseDecimal (str, ref ptr);\r
-                               if (n < 0 || n > regex.GroupCount)\r
-                                       throw new ArgumentException ("Bad group number.");\r
-                               \r
-                               return new Term (TermOp.Match, n);\r
-                       }\r
-                       \r
-                       ++ ptr;\r
-\r
-                       switch (c) {\r
-                       case '{': {                     // named group\r
-                               string name = Parser.ParseName (str, ref ptr);\r
-                               if (str[ptr ++] != '}' || name == null)\r
-                                       throw new ArgumentException ("Bad group name.");\r
-                               \r
-                               int n = regex.GroupNumberFromName (name);\r
-                               \r
-                               if (n < 0)\r
-                                       throw new ArgumentException ("Bad group name.");\r
-\r
-                               return new Term (TermOp.Match, n);\r
-                       }\r
-\r
-                       case '&':                       // entire match\r
-                               return new Term (TermOp.Match, 0);\r
-\r
-                       case '`':                       // text before match\r
-                               return new Term (TermOp.PreMatch, 0);\r
-\r
-                       case '\'':                      // text after match\r
-                               return new Term (TermOp.PostMatch, 0);\r
-\r
-                       case '+':                       // last group\r
-                               return new Term (TermOp.Match, regex.GroupCount - 1);\r
-\r
-                       case '_':                       // entire text\r
-                               return new Term (TermOp.All, 0);\r
-\r
-                       default:\r
-                               throw new ArgumentException ("Bad replacement pattern.");\r
-                       }\r
-               }\r
-\r
-               private Regex regex;\r
-               private ArrayList terms;\r
-\r
-               private enum TermOp {\r
-                       None,                           // no action\r
-                       Match,                          // input within group\r
-                       PreMatch,                       // input before group\r
-                       PostMatch,                      // input after group\r
-                       All                             // entire input\r
-               }\r
-\r
-               private class Term {\r
-                       public Term (TermOp op, int arg) {\r
-                               this.op = op;\r
-                               this.arg = arg;\r
-                               this.literal = "";\r
-                       }\r
-\r
-                       public Term (string literal) {\r
-                               this.op = TermOp.None;\r
-                               this.arg = 0;\r
-                               this.literal = literal;\r
-                       }\r
-\r
-                       public string Literal {\r
-                               set { literal = value; }\r
-                       }\r
-\r
-                       public string GetResult (Match match) {\r
-                               Group group = match.Groups[arg];\r
-                       \r
-                               switch (op) {\r
-                               case TermOp.None:\r
-                                       return literal;\r
-\r
-                               case TermOp.Match:\r
-                                       return literal + group.Value;\r
-\r
-                               case TermOp.PreMatch:\r
-                                       return literal + group.Text.Substring (0, group.Index);\r
-\r
-                               case TermOp.PostMatch:\r
-                                       return literal + group.Text.Substring (group.Index + group.Length);\r
-\r
-                               case TermOp.All:\r
-                                       return literal + group.Text;\r
-                               }\r
-\r
-                               return "";\r
-                       }\r
-               \r
-                       public TermOp op;               // term type\r
-                       public int arg;                 // group argument\r
-                       public string literal;          // literal to prepend\r
-\r
-                       public override string ToString () {\r
-                               return op.ToString () + "(" + arg + ") " + literal;\r
-                       }\r
-               }\r
-       }\r
-}\r
+
+using System;
+using System.Text;
+using System.Collections;
+
+using Parser = System.Text.RegularExpressions.Syntax.Parser;
+
+namespace System.Text.RegularExpressions {
+
+       class ReplacementEvaluator {
+               public static string Evaluate (string replacement, Match match) {
+                       ReplacementEvaluator ev = new ReplacementEvaluator (match.Regex, replacement);
+                       return ev.Evaluate (match);
+               }
+
+               public ReplacementEvaluator (Regex regex, string replacement) {
+                       this.regex = regex;
+                       this.replacement = replacement;
+                       this.pieces = null;
+                       this.n_pieces = 0;
+                       Compile ();
+               }
+
+               public string Evaluate (Match match) 
+               {
+                       StringBuilder sb = new StringBuilder ();
+                       EvaluateAppend (match, sb);
+                       return sb.ToString ();
+               }
+
+               public void EvaluateAppend (Match match, StringBuilder sb)
+               {
+                       int i = 0, k, count;
+
+                       if (n_pieces == 0) {
+                               sb.Append (replacement);
+                               return;
+                       }
+
+                       while (i < n_pieces) {
+                               k = pieces [i++];
+                               if (k >= 0) {
+                                       count = pieces [i++];
+                                       sb.Append (replacement, k, count);
+                               } else if (k < -3) {
+                                       Group group = match.Groups [-(k + 4)];
+                                       sb.Append (group.Text, group.Index, group.Length);
+                               } else if (k == -1) {
+                                       sb.Append (match.Text);
+                               } else if (k == -2) {
+                                       sb.Append (match.Text, 0, match.Index);
+                               } else { // k == -3
+                                       int matchend = match.Index + match.Length;
+                                       sb.Append (match.Text, matchend, match.Text.Length - matchend);
+                               } 
+                       }
+               }
+
+               void Ensure (int size)
+               {
+                       int new_size;
+                       if (pieces == null) {
+                               new_size = 4;
+                               if (new_size < size)
+                                       new_size = size;
+                               pieces = new int [new_size];
+                       } else if (size >= pieces.Length) {
+                               new_size = pieces.Length + (pieces.Length >> 1);
+                               if (new_size < size)
+                                       new_size = size;
+                               int [] new_pieces = new int [new_size];
+                               Array.Copy (pieces, new_pieces, n_pieces);
+                               pieces = new_pieces;
+                       }
+               }
+
+               void AddFromReplacement (int start, int end)
+               {
+                       if (start == end)
+                               return;
+                       Ensure (n_pieces + 2);
+                       pieces [n_pieces++] = start;
+                       pieces [n_pieces++] = end - start;
+               }
+
+               void AddInt (int i)
+               {
+                       Ensure (n_pieces + 1);
+                       pieces [n_pieces++] = i;
+               }
+
+               // private
+               private void Compile () {
+                       replacement = Parser.Unescape (replacement);
+
+                       int anchor = 0, ptr = 0, saveptr;
+                       char c;
+                       while (ptr < replacement.Length) {
+                               c = replacement [ptr++];
+
+                               if (c != '$')
+                                       continue;
+
+                               // If the '$' was the last character, just emit it as is
+                               if (ptr == replacement.Length)
+                                       break;
+
+                               // If we saw a '$$'
+                               if (replacement [ptr] == '$') {
+                                       // Everthing from 'anchor' upto and including the first '$' is copied from the replacement string
+                                       AddFromReplacement (anchor, ptr);
+                                       // skip over the second '$'.
+                                       anchor = ++ptr;
+                                       continue;
+                               }
+
+                               saveptr = ptr - 1;
+
+                               int from_match = CompileTerm (ref ptr);
+
+                               // We couldn't recognize the term following the '$'.  Just treat it as a literal.
+                               // 'ptr' has already been advanced, no need to rewind it back
+                               if (from_match >= 0)
+                                       continue;
+
+                               AddFromReplacement (anchor, saveptr);
+                               AddInt (from_match);
+                               anchor = ptr;
+                       }
+
+                       // If we never needed to advance anchor, it means the result is the whole replacement string.
+                       // We optimize that case by never allocating the pieces array.
+                       if (anchor != 0)
+                               AddFromReplacement (anchor, ptr);
+               }
+
+               private int CompileTerm (ref int ptr) {
+                       char c = replacement [ptr];
+
+                       if (Char.IsDigit (c)) {         // numbered group
+                               int n = Parser.ParseDecimal (replacement, ref ptr);
+                               if (n < 0 || n > regex.GroupCount)
+                                       return 0;
+                               
+                               return -n - 4;
+                       }
+                       
+                       ++ ptr;
+
+                       switch (c) {
+                       case '{': {                     // named group
+                               string name;
+                               int n = -1;
+
+                               try {
+                                       // The parser is written such that there are few explicit range checks
+                                       // and depends on 'IndexOutOfRangeException' being thrown.
+
+                                       if (Char.IsDigit (replacement [ptr])) {
+                                               n = Parser.ParseDecimal (replacement, ref ptr);
+                                               name = "";
+                                       } else {
+                                               name = Parser.ParseName (replacement, ref ptr);
+                                       }
+                               } catch (IndexOutOfRangeException) {
+                                       ptr = replacement.Length;
+                                       return 0;
+                               }
+
+                               if (ptr == replacement.Length || replacement[ptr] != '}' || name == null)
+                                       return 0;
+                               ++ptr;                  // Swallow the '}'
+
+                               if (name != "")
+                                       n = regex.GroupNumberFromName (name);
+
+                               if (n < 0 || n > regex.GroupCount)
+                                       return 0;
+
+                               return -n - 4;
+                       }
+
+                       case '&':                       // entire match.  Value should be same as $0
+                               return -4;
+
+                       case '`':                       // text before match
+                               return -2;
+
+                       case '\'':                      // text after match
+                               return -3;
+
+                       case '+':                       // last group
+                               return -regex.GroupCount - 4;
+
+                       case '_':                       // entire text
+                               return -1;
+
+                       default:
+                               return 0;
+                       }
+               }
+
+               private Regex regex;
+               int n_pieces;
+               private int [] pieces;
+               string replacement;
+       }
+}