Support RegexOptions.RightToLeft in Replace().
authorRaja R Harinath <harinath@hurrynot.org>
Tue, 6 Nov 2007 22:03:35 +0000 (22:03 -0000)
committerRaja R Harinath <harinath@hurrynot.org>
Tue, 6 Nov 2007 22:03:35 +0000 (22:03 -0000)
In b/class/System/System.Text.RegularExpressions:
* BaseMachine.cs (Replace): Use either LTRReplace or RTLReplace
based on regex.
(LTRReplace): Make internal and rename the MatchAppendEvaluator
version of Replace to this.
(RTLReplace): New.
* Regex.cs (Replace): Use LTRReplace and RTLReplace from BaseMachine.
* replace.cs (ReplacementEvaluator.Evaluate): Optimize simple case.
Based on patch by Stephane Delcroix.

In b/class/System/Test/System.Text.RegularExpressions:
* MatchTest.cs (Match_Backref): New.
* RegexReplace.cs (direction, testcase.direction): New.
(testcase..ctor): Allow specifying the direction of the replace.
(ReplaceTests): Test replace in both directions.
(EvaluatorTests): New test based on #321036.

svn path=/trunk/mcs/; revision=89034

mcs/class/System/System.Text.RegularExpressions/BaseMachine.cs
mcs/class/System/System.Text.RegularExpressions/ChangeLog
mcs/class/System/System.Text.RegularExpressions/Regex.cs
mcs/class/System/System.Text.RegularExpressions/replace.cs
mcs/class/System/Test/System.Text.RegularExpressions/ChangeLog
mcs/class/System/Test/System.Text.RegularExpressions/MatchTest.cs
mcs/class/System/Test/System.Text.RegularExpressions/RegexReplace.cs

index 45795a68fe4622bde20fe3e82c6b5785ca94ebbe..934cffe756ae9f155d6b556a027e36d5dffe65bd 100644 (file)
-//\r
-// BaseMachine.jvm.cs\r
-//\r
-// Author:\r
-// author:     Dan Lewis (dlewis@gmx.co.uk)\r
-//             (c) 2002\r
-// Copyright (C) 2005 Novell, Inc (http://www.novell.com)\r
-//\r
-\r
-//\r
-// Permission is hereby granted, free of charge, to any person obtaining\r
-// a copy of this software and associated documentation files (the\r
-// "Software"), to deal in the Software without restriction, including\r
-// without limitation the rights to use, copy, modify, merge, publish,\r
-// distribute, sublicense, and/or sell copies of the Software, and to\r
-// permit persons to whom the Software is furnished to do so, subject to\r
-// the following conditions:\r
-// \r
-// The above copyright notice and this permission notice shall be\r
-// included in all copies or substantial portions of the Software.\r
-// \r
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
-//\r
-\r
-\r
-\r
-using System;\r
-using System.Collections;\r
-\r
-namespace System.Text.RegularExpressions\r
-{\r
-       abstract class BaseMachine : IMachine\r
-       {\r
-               delegate void MatchAppendEvaluator (Match match, StringBuilder sb);\r
-\r
-               virtual public string Replace (Regex regex, string input, string replacement, int count, int startat) {\r
-                       ReplacementEvaluator ev = new ReplacementEvaluator (regex, replacement);\r
-                       return Replace (regex, input, new MatchAppendEvaluator (ev.EvaluateAppend), count, startat);\r
-               }\r
-\r
-               virtual public string [] Split (Regex regex, string input, int count, int startat) {\r
-                       ArrayList splits = new ArrayList ();\r
-                       if (count == 0)\r
-                               count = Int32.MaxValue;\r
-\r
-                       int ptr = startat;\r
-                       Match m = null;\r
-                       while (--count > 0) {\r
-                               if (m != null)\r
-                                       m = m.NextMatch ();\r
-                               else\r
-                                       m = regex.Match (input, ptr);\r
-\r
-                               if (!m.Success)\r
-                                       break;\r
-\r
-                               if (regex.RightToLeft)\r
-                                       splits.Add (input.Substring (m.Index + m.Length, ptr - m.Index - m.Length));\r
-                               else\r
-                                       splits.Add (input.Substring (ptr, m.Index - ptr));\r
-\r
-                               int gcount = m.Groups.Count;\r
-                               for (int gindex = 1; gindex < gcount; gindex++) {\r
-                                       Group grp = m.Groups [gindex];\r
-                                       splits.Add (input.Substring (grp.Index, grp.Length));\r
-                               }\r
-\r
-                               if (regex.RightToLeft)\r
-                                       ptr = m.Index;\r
-                               else\r
-                                       ptr = m.Index + m.Length;\r
-\r
-                       }\r
-\r
-                       if (regex.RightToLeft && ptr >= 0)\r
-                               splits.Add (input.Substring (0, ptr));\r
-                       if (!regex.RightToLeft && ptr <= input.Length)\r
-                               splits.Add (input.Substring (ptr));\r
-\r
-                       return (string []) splits.ToArray (typeof (string));\r
-               }\r
-\r
-               virtual public Match Scan (Regex regex, string text, int start, int end) {\r
-                       throw new NotImplementedException ("Scan method must be implemented in derived classes");\r
-               }\r
-\r
-               virtual public string Result (string replacement, Match match)\r
-               {\r
-                       return ReplacementEvaluator.Evaluate (replacement, match);\r
-               }\r
-\r
-               private static string Replace (Regex regex, string input, MatchAppendEvaluator evaluator, int count, int startat) {\r
-                       StringBuilder result = new StringBuilder ();\r
-                       int ptr = startat;\r
-                       int counter = count;\r
-\r
-                       result.Append (input, 0, ptr);\r
-\r
-                       Match m = regex.Match (input, startat);\r
-                       while (m.Success) {\r
-                               if (count != -1)\r
-                                       if (counter-- <= 0)\r
-                                               break;\r
-                               if (m.Index < ptr)\r
-                                       throw new SystemException ("how");\r
-                               result.Append (input, ptr, m.Index - ptr);\r
-                               evaluator (m, result);\r
-\r
-                               ptr = m.Index + m.Length;\r
-                               m = m.NextMatch ();\r
-                       }\r
-\r
-                       if (ptr == 0)\r
-                               return input;\r
-\r
-                       result.Append (input, ptr, input.Length - ptr);\r
-\r
-                       return result.ToString ();\r
-               }\r
-       }\r
-}\r
+//
+// BaseMachine.jvm.cs
+//
+// Author:
+// author:     Dan Lewis (dlewis@gmx.co.uk)
+//             (c) 2002
+// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+
+namespace System.Text.RegularExpressions
+{
+       abstract class BaseMachine : IMachine
+       {
+               internal delegate void MatchAppendEvaluator (Match match, StringBuilder sb);
+
+               public virtual string Replace (Regex regex, string input, string replacement, int count, int startat)
+               {
+                       ReplacementEvaluator ev = new ReplacementEvaluator (regex, replacement);
+                       if (regex.RightToLeft)
+                               return RTLReplace (regex, input, new MatchEvaluator (ev.Evaluate), count, startat);
+                       else
+                               return LTRReplace (regex, input, new MatchAppendEvaluator (ev.EvaluateAppend), count, startat);
+               }
+
+               virtual public string [] Split (Regex regex, string input, int count, int startat)
+               {
+                       ArrayList splits = new ArrayList ();
+                       if (count == 0)
+                               count = Int32.MaxValue;
+
+                       int ptr = startat;
+                       Match m = null;
+                       while (--count > 0) {
+                               if (m != null)
+                                       m = m.NextMatch ();
+                               else
+                                       m = regex.Match (input, ptr);
+
+                               if (!m.Success)
+                                       break;
+
+                               if (regex.RightToLeft)
+                                       splits.Add (input.Substring (m.Index + m.Length, ptr - m.Index - m.Length));
+                               else
+                                       splits.Add (input.Substring (ptr, m.Index - ptr));
+
+                               int gcount = m.Groups.Count;
+                               for (int gindex = 1; gindex < gcount; gindex++) {
+                                       Group grp = m.Groups [gindex];
+                                       splits.Add (input.Substring (grp.Index, grp.Length));
+                               }
+
+                               if (regex.RightToLeft)
+                                       ptr = m.Index;
+                               else
+                                       ptr = m.Index + m.Length;
+
+                       }
+
+                       if (regex.RightToLeft && ptr >= 0)
+                               splits.Add (input.Substring (0, ptr));
+                       if (!regex.RightToLeft && ptr <= input.Length)
+                               splits.Add (input.Substring (ptr));
+
+                       return (string []) splits.ToArray (typeof (string));
+               }
+
+               virtual public Match Scan (Regex regex, string text, int start, int end)
+               {
+                       throw new NotImplementedException ("Scan method must be implemented in derived classes");
+               }
+
+               virtual public string Result (string replacement, Match match)
+               {
+                       return ReplacementEvaluator.Evaluate (replacement, match);
+               }
+
+               internal static string LTRReplace (Regex regex, string input, MatchAppendEvaluator evaluator, int count, int startat)
+               {
+                       StringBuilder result = new StringBuilder ();
+                       int ptr = startat;
+                       int counter = count;
+
+                       result.Append (input, 0, ptr);
+
+                       Match m = regex.Match (input, startat);
+                       while (m.Success) {
+                               if (count != -1)
+                                       if (counter-- <= 0)
+                                               break;
+                               if (m.Index < ptr)
+                                       throw new SystemException ("how");
+                               result.Append (input, ptr, m.Index - ptr);
+                               evaluator (m, result);
+
+                               ptr = m.Index + m.Length;
+                               m = m.NextMatch ();
+                       }
+
+                       if (ptr == startat)
+                               return input;
+
+                       result.Append (input, ptr, input.Length - ptr);
+
+                       return result.ToString ();
+               }
+
+               internal static string RTLReplace (Regex regex, string input, MatchEvaluator evaluator, int count, int startat)
+               {
+                       int ptr = startat;
+                       int counter = count;
+
+                       StringCollection pieces = new StringCollection ();
+                       pieces.Add (input.Substring (ptr));
+
+                       Match m = regex.Match (input, startat);
+                       while (m.Success) {
+                               if (count != -1)
+                                       if (counter-- <= 0)
+                                               break;
+                               if (m.Index + m.Length > ptr)
+                                       throw new SystemException ("how");
+                               pieces.Add (input.Substring (m.Index + m.Length, ptr - m.Index - m.Length));
+                               pieces.Add (evaluator (m));
+
+                               ptr = m.Index;
+                               m = m.NextMatch ();
+                       }
+
+                       if (ptr == startat)
+                               return input;
+
+                       StringBuilder result = new StringBuilder ();
+
+                       result.Append (input, 0, ptr);
+                       for (int i = pieces.Count; i > 0; )
+                               result.Append (pieces [--i]);
+
+                       pieces.Clear ();
+
+                       return result.ToString ();
+               }
+       }
+}
index ab1902e7f0747d8d33fee21609f0505e009c16a0..4fce74f9f5bbe05f1a1946cb81a7b364012182a5 100644 (file)
@@ -1,5 +1,15 @@
 2007-11-07  Raja R Harinath  <harinath@gmail.com>
 
+       Support RegexOptions.RightToLeft in Replace().
+       * BaseMachine.cs (Replace): Use either LTRReplace or RTLReplace
+       based on regex.
+       (LTRReplace): Make internal and rename the MatchAppendEvaluator
+       version of Replace to this.
+       (RTLReplace): New.
+       * Regex.cs (Replace): Use LTRReplace and RTLReplace from BaseMachine.
+       * replace.cs (ReplacementEvaluator.Evaluate): Optimize simple case.
+       Based on patch by Stephane Delcroix.
+
        * replace.cs (Compile): Don't unescape string.
 
 2007-11-01  Gert Driesen  <drieseng@users.sourceforge.net>
index 7251db8bc188f0bfeee475a9682e37b64e08d366..c9f3a61f4fd1213ae8a1b50ac9166215aa5a5d27 100644 (file)
@@ -373,42 +373,17 @@ namespace System.Text.RegularExpressions {
                        public void Evaluate (Match m, StringBuilder sb) { sb.Append (ev (m)); }
                }
 
-               delegate void MatchAppendEvaluator (Match match, StringBuilder sb);
-
                public string Replace (string input, MatchEvaluator evaluator, int count, int startat)
                {
-                       Adapter a = new Adapter (evaluator);
-                       return Replace (input, new MatchAppendEvaluator (a.Evaluate), count, startat);
-               }
-
-               string Replace (string input, MatchAppendEvaluator evaluator, int count, int startat)
-               {
-                       StringBuilder result = new StringBuilder ();
-                       int ptr = startat;
-                       int counter = count;
-
-                       result.Append (input, 0, ptr);
+                       if (RightToLeft)
+                               return BaseMachine.RTLReplace (this, input, evaluator, count, startat);
 
-                       Match m = Match (input, startat);
-                       while (m.Success) {
-                               if (count != -1)
-                                       if(counter -- <= 0)
-                                               break;
-                               if (m.Index < ptr)
-                                       throw new SystemException ("how");
-                               result.Append (input, ptr, m.Index - ptr);
-                               evaluator (m, result);
-
-                               ptr = m.Index + m.Length;
-                               m = m.NextMatch ();
-                       }
-                       
-                       if (ptr == 0)
-                               return input;
-                       
-                       result.Append (input, ptr, input.Length - ptr);
+                       // NOTE: If this is a cause of a lot of allocations, we can convert it to
+                       //       use a ThreadStatic allocation mitigator
+                       Adapter a = new Adapter (evaluator);
 
-                       return result.ToString ();
+                       return BaseMachine.LTRReplace (this, input, new BaseMachine.MatchAppendEvaluator (a.Evaluate),
+                                                      count, startat);
                }
 
                public string Replace (string input, string replacement)
index aaf6aaf931e5ab9c9af2fed0dcb10f9e3b9cefd2..c27f6479cda026967e883ae7db061d410a7889dc 100644 (file)
@@ -53,6 +53,8 @@ namespace System.Text.RegularExpressions {
 
                public string Evaluate (Match match) 
                {
+                       if (n_pieces == 0)
+                               return replacement;
                        StringBuilder sb = new StringBuilder ();
                        EvaluateAppend (match, sb);
                        return sb.ToString ();
@@ -60,17 +62,16 @@ namespace System.Text.RegularExpressions {
 
                public void EvaluateAppend (Match match, StringBuilder sb)
                {
-                       int i = 0, k, count;
-
                        if (n_pieces == 0) {
                                sb.Append (replacement);
                                return;
                        }
 
+                       int i = 0;
                        while (i < n_pieces) {
-                               k = pieces [i++];
+                               int k = pieces [i++];
                                if (k >= 0) {
-                                       count = pieces [i++];
+                                       int count = pieces [i++];
                                        sb.Append (replacement, k, count);
                                } else if (k < -3) {
                                        Group group = match.Groups [-(k + 4)];
index 117abfb28d60366372dc06c93561ffdb517de359..72ce49d98b3831a8e1ac17031d4429124ee30d52 100644 (file)
@@ -1,5 +1,11 @@
 2007-11-07  Raja R Harinath  <harinath@gmail.com>
 
+       * MatchTest.cs (Match_Backref): New.
+       * RegexReplace.cs (direction, testcase.direction): New.
+       (testcase..ctor): Allow specifying the direction of the replace.
+       (ReplaceTests): Test replace in both directions.
+       (EvaluatorTests): New test based on #321036.
+
        * RegexReplace.cs (tests): New test from #317092.
        (ReplaceTests): Don't catch an AssertException.
 
index c8601805d0645d0044d93be2528b3fd587021093..58a9744adb30cac91db228d7386460749deb71a4 100644 (file)
@@ -96,5 +96,13 @@ namespace MonoTests.System.Text.RegularExpressions
                                Assert.IsNotNull (ex.Message, "#4");
                        }
                }
+
+               [Test]
+               public void Match_Backref ()
+               {
+                       Assert.IsTrue (Regex.IsMatch ("F2345678910LL1", @"(F)(2)(3)(4)(5)(6)(7)(8)(9)(10)(L)\11"), "ltr");
+                       // FIXME
+                       //Assert.IsTrue (Regex.IsMatch ("F2345678910LL1", @"(F)(2)(3)(4)(5)(6)(7)(8)(9)(10)(L)\11", RegexOptions.RightToLeft), "rtl");
+               }
        }
 }
index c4e0bd9b4bdd1924956afe1e7ff3f8544b70cc13..9b8d3800368024ce77ed66acb515cb9af558bcdb 100644 (file)
@@ -15,19 +15,28 @@ namespace MonoTests.System.Text.RegularExpressions {
 
        [TestFixture]
        public class RegexReplaceTest {
-               struct testcase { 
+               [Flags]
+               enum direction {
+                       LTR = 1,
+                       RTL = 2,
+                       Both = 3
+               }
+               struct testcase {
                        public string original, pattern, replacement, expected;
-                       public testcase (string o, string p, string r, string e)
+                       public direction direction;
+                       public testcase (string o, string p, string r, string e, direction d)
                        {
                                original = o;
                                pattern = p;
                                replacement = r;
                                expected = e;
+                               direction = d;
                        }
+                       public testcase (string o, string p, string r, string e) : this (o, p, r, e, direction.Both) {}
                }
-               
+
                static testcase [] tests = {
-                       //      original        pattern                 replacement             expected
+                       //      original        pattern                 replacement             expected
                        new testcase ("text",   "x",                    "y",                    "teyt"          ),
                        new testcase ("text",   "x",                    "$",                    "te$t"          ),
                        new testcase ("text",   "x",                    "$1",                   "te$1t"         ),
@@ -58,21 +67,24 @@ namespace MonoTests.System.Text.RegularExpressions {
                        new testcase ("text",   "e(?<foo>x)",           "$${foo}}",             "t${foo}}t"     ),
                        new testcase ("text",   "e(?<foo>x)",           "$${foo}",              "t${foo}t"      ),
                        new testcase ("text",   "e(?<foo>x)",           "$$",                   "t$t"           ),
-                       new testcase ("text",   "(?<foo>e)(?<foo>x)",   "${foo}$1$2",           "txx$2t"        ),
-                       new testcase ("text",   "(e)(?<foo>x)",         "${foo}$1$2",           "txext"         ),
-                       new testcase ("text",   "(?<foo>e)(x)",         "${foo}$1$2",           "texet"         ),
-                       new testcase ("text",   "(e)(?<foo>x)",         "${foo}$1$2$+",         "txexxt"        ),
-                       new testcase ("text",   "(?<foo>e)(x)",         "${foo}$1$2$+",         "texeet"        ),
-                       new testcase ("314 1592 65358",         @"\d\d\d\d|\d\d\d", "a",        "a a a8"        ),
-                       new testcase ("2 314 1592 65358",       @"\d\d\d\d|\d\d\d", "a",        "2 a a a8"      ),
-                       new testcase ("<i>am not</i>",          "<(.+?)>",      "[$0:$1]",      "[<i>:i]am not[</i>:/i]"),
-                       new testcase ("texts",  "(?<foo>e)(x)",         "${foo}$1$2$_",         "texetextsts"   ),
-                       new testcase ("texts",  "(?<foo>e)(x)",         "${foo}$1$2$`",         "texetts"       ),
-                       new testcase ("texts",  "(?<foo>e)(x)",         "${foo}$1$2$'",         "texetsts"      ),
-                       new testcase ("texts",  "(?<foo>e)(x)",         "${foo}$1$2$&",         "texeexts"      ),
-                       //new testcase ("F2345678910L71",       @"(F)(2)(3)(4)(5)(6)(?<S>7)(8)(9)(10)(L)\11",   "${S}$11$1", "77F1"     ),
-                       new testcase ("F2345678910L71", @"(F)(2)(3)(4)(5)(6)(7)(8)(9)(10)(L)\11",       "${S}$11$1", "F2345678910L71"   ),
-                       new testcase ("F2345678910LL1", @"(F)(2)(3)(4)(5)(6)(7)(8)(9)(10)(L)\11",       "${S}$11$1", "${S}LF1"  ),
+                       new testcase ("text",   "(?<foo>e)(?<foo>x)",   "${foo}$1$2",           "txx$2t", direction.LTR),
+                       new testcase ("text",   "(?<foo>e)(?<foo>x)",   "${foo}$1$2",           "tee$2t", direction.RTL),
+                       new testcase ("text",   "(e)(?<foo>x)", "${foo}$1$2",           "txext" ),
+                       new testcase ("text",   "(?<foo>e)(x)", "${foo}$1$2",           "texet" ),
+                       new testcase ("text",   "(e)(?<foo>x)", "${foo}$1$2$+",         "txexxt"        ),
+                       new testcase ("text",   "(?<foo>e)(x)", "${foo}$1$2$+",         "texeet"        ),
+                       new testcase ("314 1592 65358",         @"\d\d\d\d|\d\d\d", "a",        "a a a8", direction.LTR),
+                       new testcase ("314 1592 65358",         @"\d\d\d\d|\d\d\d", "a",        "a a 6a", direction.RTL),
+                       new testcase ("2 314 1592 65358",       @"\d\d\d\d|\d\d\d", "a",        "2 a a a8", direction.LTR),
+                       new testcase ("2 314 1592 65358",       @"\d\d\d\d|\d\d\d", "a",        "2 a a 6a", direction.RTL),
+                       new testcase ("<i>am not</i>",          "<(.+?)>",      "[$0:$1]",      "[<i>:i]am not[</i>:/i]"),
+                       new testcase ("texts",  "(?<foo>e)(x)", "${foo}$1$2$_",         "texetextsts"   ),
+                       new testcase ("texts",  "(?<foo>e)(x)", "${foo}$1$2$`",         "texetts"       ),
+                       new testcase ("texts",  "(?<foo>e)(x)", "${foo}$1$2$'",         "texetsts"      ),
+                       new testcase ("texts",  "(?<foo>e)(x)", "${foo}$1$2$&",         "texeexts"      ),
+                       //new testcase ("F2345678910L71",       @"(F)(2)(3)(4)(5)(6)(?<S>7)(8)(9)(10)(L)\11",   "${S}$11$1", "77F1"     ),
+                       new testcase ("F2345678910L71", @"(F)(2)(3)(4)(5)(6)(7)(8)(9)(10)(L)\11",       "${S}$11$1", "F2345678910L71"   ),
+                       new testcase ("F2345678910LL1", @"(F)(2)(3)(4)(5)(6)(7)(8)(9)(10)(L)\11",       "${S}$11$1", "${S}LF1", /*FIXME: this should work in RTL direction too */ direction.LTR),
                        new testcase ("a", "a", @"\\", @"\\"),
                };
 
@@ -82,15 +94,53 @@ namespace MonoTests.System.Text.RegularExpressions {
                        string result = "";
                        int i = 0;
                        foreach (testcase test in tests) {
-                               try {
-                                       result = Regex.Replace (test.original, test.pattern, test.replacement);
-                               } catch (Exception e) {
-                                       Assert.Fail ("rr#{0} Exception thrown: " + e.ToString (), i);
+                               if ((test.direction & direction.LTR) == direction.LTR) {
+                                       try {
+                                               result = Regex.Replace (test.original, test.pattern, test.replacement);
+                                       } catch (Exception e) {
+                                               Assert.Fail ("rr#{0}ltr Exception thrown: " + e.ToString (), i);
+                                       }
+                                       Assert.AreEqual (test.expected, result, "rr#{0}ltr: {1} ~ s,{2},{3},", i,
+                                                        test.original, test.pattern, test.replacement);
+                               }
+
+                               if ((test.direction & direction.RTL) == direction.RTL) {
+                                       try {
+                                               result = Regex.Replace (test.original, test.pattern, test.replacement, RegexOptions.RightToLeft);
+                                       } catch (Exception e) {
+                                               Assert.Fail ("rr#{0}rtl Exception thrown: " + e.ToString (), i);
+                                       }
+                                       Assert.AreEqual (test.expected, result, "rr#{0}rtl: {1} ~ s,{2},{3},", i,
+                                                        test.original, test.pattern, test.replacement);
                                }
-                               Assert.AreEqual (result, test.expected, "rr#{0}: {1} ~ s,{2},{3},", i,
-                                                test.original, test.pattern, test.replacement);
                                ++i;
                        }
                }
+
+               static string substitute (Match m)
+               {
+                       switch (m.Value.Substring(2, m.Length - 3)) {
+                       case "foo": return "bar";
+                       case "hello": return "world";
+                       default: return "ERROR";
+                       }
+               }
+
+               static string resolve (string val, Regex r)
+               {
+                       MatchEvaluator ev = new MatchEvaluator (substitute);
+                       while (r.IsMatch (val))
+                               val = r.Replace (val, ev);
+                       return val;
+               }
+
+               [Test] // #321036
+               public void EvaluatorTests ()
+               {
+                       string test = "@(foo) @(hello)";
+                       string regex = "\\@\\([^\\)]*?\\)";
+                       Assert.AreEqual ("bar world", resolve (test, new Regex (regex)), "ltr");
+                       Assert.AreEqual ("bar world", resolve (test, new Regex (regex, RegexOptions.RightToLeft)), "rtl");
+               }
        }
 }