The Labs \ Source Viewer \ SSCLI \ System.Text.RegularExpressions \ RegexParser

  1. //------------------------------------------------------------------------------
  2. // <copyright file="RegexParser.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. // This RegexParser class is internal to the Regex package.
  16. // It builds a tree of RegexNodes from a regular expression
  17. // Implementation notes:
  18. //
  19. // It would be nice to get rid of the comment modes, since the
  20. // ScanBlank() calls are just kind of duct-taped in.
  21. namespace System.Text.RegularExpressions
  22. {
  23.    
  24.     using System.Collections;
  25.     using System.Globalization;
  26.    
  27.     internal sealed class RegexParser
  28.     {
  29.         internal RegexNode _stack;
  30.         internal RegexNode _group;
  31.         internal RegexNode _alternation;
  32.         internal RegexNode _concatenation;
  33.         internal RegexNode _unit;
  34.        
  35.         internal string _pattern;
  36.         internal int _currentPos;
  37.         internal CultureInfo _culture;
  38.        
  39.         internal int _autocap;
  40.         internal int _capcount;
  41.         internal int _captop;
  42.         internal int _capsize;
  43.         internal Hashtable _caps;
  44.         internal Hashtable _capnames;
  45.         internal object[] _capnumlist;
  46.         internal ArrayList _capnamelist;
  47.        
  48.         internal RegexOptions _options;
  49.         internal ArrayList _optionsStack;
  50.        
  51.         internal bool _ignoreNextParen = false;
  52.        
  53.         internal const int MaxValueDiv10 = Int32.MaxValue / 10;
  54.         internal const int MaxValueMod10 = Int32.MaxValue % 10;
  55.        
  56. /*
  57.         * This static call constructs a RegexTree from a regular expression
  58.         * pattern string and an option string.
  59.         *
  60.         * The method creates, drives, and drops a parser instance.
  61.         */       
  62.         static internal RegexTree Parse(string re, RegexOptions op)
  63.         {
  64.             RegexParser p;
  65.             RegexNode root;
  66.             string[] capnamelist;
  67.            
  68.             p = new RegexParser((op & RegexOptions.CultureInvariant) != 0 ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
  69.            
  70.             p._options = op;
  71.            
  72.             p.SetPattern(re);
  73.             p.CountCaptures();
  74.             p.Reset(op);
  75.             root = p.ScanRegex();
  76.            
  77.             if (p._capnamelist == null)
  78.                 capnamelist = null;
  79.             else
  80.                 capnamelist = (string[])p._capnamelist.ToArray(typeof(string));
  81.            
  82.             return new RegexTree(root, p._caps, p._capnumlist, p._captop, p._capnames, capnamelist, op);
  83.         }
  84.        
  85. /*
  86.         * This static call constructs a flat concatenation node given
  87.         * a replacement pattern.
  88.         */       
  89.         static internal RegexReplacement ParseReplacement(string rep, Hashtable caps, int capsize, Hashtable capnames, RegexOptions op)
  90.         {
  91.             RegexParser p;
  92.             RegexNode root;
  93.            
  94.             p = new RegexParser((op & RegexOptions.CultureInvariant) != 0 ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
  95.            
  96.             p._options = op;
  97.            
  98.             p.NoteCaptures(caps, capsize, capnames);
  99.             p.SetPattern(rep);
  100.             root = p.ScanReplacement();
  101.            
  102.             return new RegexReplacement(rep, root, caps);
  103.         }
  104.        
  105. /*
  106.         * Escapes all metacharacters (including |,(,),[,{,|,^,$,*,+,?,\, spaces and #)
  107.         */       
  108.         static internal string Escape(string input)
  109.         {
  110.             for (int i = 0; i < input.Length; i++) {
  111.                 if (IsMetachar(input[i])) {
  112.                     StringBuilder sb = new StringBuilder();
  113.                     char ch = input[i];
  114.                     int lastpos;
  115.                    
  116.                     sb.Append(input, 0, i);
  117.                     do {
  118.                         sb.Append('\\');
  119.                         switch (ch) {
  120.                             case '\n':
  121.                                 ch = 'n';
  122.                                 break;
  123.                             case '\r':
  124.                                 ch = 'r';
  125.                                 break;
  126.                             case '\t':
  127.                                 ch = 't';
  128.                                 break;
  129.                             case '\f':
  130.                                 ch = 'f';
  131.                                 break;
  132.                         }
  133.                         sb.Append(ch);
  134.                         i++;
  135.                         lastpos = i;
  136.                        
  137.                         while (i < input.Length) {
  138.                             ch = input[i];
  139.                             if (IsMetachar(ch))
  140.                                 break;
  141.                            
  142.                             i++;
  143.                         }
  144.                        
  145.                         sb.Append(input, lastpos, i - lastpos);
  146.                        
  147.                     }
  148.                     while (i < input.Length);
  149.                    
  150.                     return sb.ToString();
  151.                 }
  152.             }
  153.            
  154.             return input;
  155.         }
  156.        
  157. /*
  158.         * Escapes all metacharacters (including (,),[,],{,},|,^,$,*,+,?,\, spaces and #)
  159.         */       
  160.         static internal string Unescape(string input)
  161.         {
  162.             for (int i = 0; i < input.Length; i++) {
  163.                 if (input[i] == '\\') {
  164.                     StringBuilder sb = new StringBuilder();
  165.                     RegexParser p = new RegexParser(CultureInfo.InvariantCulture);
  166.                     int lastpos;
  167.                     p.SetPattern(input);
  168.                    
  169.                     sb.Append(input, 0, i);
  170.                     do {
  171.                         i++;
  172.                         p.Textto(i);
  173.                         if (i < input.Length)
  174.                             sb.Append(p.ScanCharEscape());
  175.                         i = p.Textpos();
  176.                         lastpos = i;
  177.                         while (i < input.Length && input[i] != '\\')
  178.                             i++;
  179.                         sb.Append(input, lastpos, i - lastpos);
  180.                        
  181.                     }
  182.                     while (i < input.Length);
  183.                    
  184.                     return sb.ToString();
  185.                 }
  186.             }
  187.            
  188.             return input;
  189.         }
  190.        
  191. /*
  192.         * Private constructor.
  193.         */       
  194.         private RegexParser(CultureInfo culture)
  195.         {
  196.             _culture = culture;
  197.             _optionsStack = new ArrayList();
  198.             _caps = new Hashtable();
  199.         }
  200.        
  201. /*
  202.         * Drops a string into the pattern buffer.
  203.         */       
  204.         internal void SetPattern(string Re)
  205.         {
  206.             if (Re == null)
  207.                 Re = String.Empty;
  208.             _pattern = Re;
  209.             _currentPos = 0;
  210.         }
  211.        
  212. /*
  213.         * Resets parsing to the beginning of the pattern.
  214.         */       
  215.         internal void Reset(RegexOptions topopts)
  216.         {
  217.             _currentPos = 0;
  218.             _autocap = 1;
  219.             _ignoreNextParen = false;
  220.            
  221.             if (_optionsStack.Count > 0)
  222.                 _optionsStack.RemoveRange(0, _optionsStack.Count - 1);
  223.            
  224.             _options = topopts;
  225.             _stack = null;
  226.         }
  227.        
  228. /*
  229.         * The main parsing function.
  230.         */       
  231.         internal RegexNode ScanRegex()
  232.         {
  233.             char ch = '@';
  234.             // nonspecial ch, means at beginning
  235.             bool isQuantifier = false;
  236.            
  237.             StartGroup(new RegexNode(RegexNode.Capture, _options, 0, -1));
  238.            
  239.             while (CharsRight() > 0) {
  240.                 bool wasPrevQuantifier = isQuantifier;
  241.                 isQuantifier = false;
  242.                
  243.                 ScanBlank();
  244.                
  245.                 int startpos = Textpos();
  246.                
  247.                 // move past all of the normal characters. We'll stop when we hit some kind of control character,
  248.                 // or if IgnorePatternWhiteSpace is on, we'll stop when we see some whitespace.
  249.                 if (UseOptionX())
  250.                     while (CharsRight() > 0 && (!IsStopperX(ch = RightChar()) || ch == '{' && !IsTrueQuantifier()))
  251.                         MoveRight();
  252.                 else
  253.                     while (CharsRight() > 0 && (!IsSpecial(ch = RightChar()) || ch == '{' && !IsTrueQuantifier()))
  254.                         MoveRight();
  255.                
  256.                 int endpos = Textpos();
  257.                
  258.                 ScanBlank();
  259.                
  260.                 if (CharsRight() == 0)
  261.                     ch = '!';
  262.                 // nonspecial, means at end
  263.                 else if (IsSpecial(ch = RightChar())) {
  264.                     isQuantifier = IsQuantifier(ch);
  265.                     MoveRight();
  266.                 }
  267.                 else
  268.                     ch = ' ';
  269.                 // nonspecial, means at ordinary char
  270.                 if (startpos < endpos) {
  271.                     int cchUnquantified = endpos - startpos - (isQuantifier ? 1 : 0);
  272.                    
  273.                     wasPrevQuantifier = false;
  274.                    
  275.                     if (cchUnquantified > 0)
  276.                         AddConcatenate(startpos, cchUnquantified, false);
  277.                    
  278.                     if (isQuantifier)
  279.                         AddUnitOne(CharAt(endpos - 1));
  280.                 }
  281.                
  282.                 switch (ch) {
  283.                     case '!':
  284.                         goto BreakOuterScan;
  285.                         break;
  286.                     case ' ':
  287.                        
  288.                         goto ContinueOuterScan;
  289.                         break;
  290.                     case '[':
  291.                        
  292.                         AddUnitSet(ScanCharClass(UseOptionI()).ToStringClass());
  293.                         break;
  294.                     case '(':
  295.                        
  296.                        
  297.                         {
  298.                             RegexNode grouper;
  299.                            
  300.                             PushOptions();
  301.                            
  302.                             if (null == (grouper = ScanGroupOpen())) {
  303.                                 PopKeepOptions();
  304.                             }
  305.                             else {
  306.                                 PushGroup();
  307.                                 StartGroup(grouper);
  308.                             }
  309.                         }
  310.                         continue;
  311.                     case '|':
  312.                        
  313.                         AddAlternate();
  314.                         goto ContinueOuterScan;
  315.                         break;
  316.                     case ')':
  317.                        
  318.                         if (EmptyStack())
  319.                             throw MakeException(SR.GetString(SR.TooManyParens));
  320.                        
  321.                         AddGroup();
  322.                         PopGroup();
  323.                         PopOptions();
  324.                        
  325.                         if (Unit() == null)
  326.                             goto ContinueOuterScan;
  327.                         break;
  328.                     case '\\':
  329.                        
  330.                         AddUnitNode(ScanBackslash());
  331.                         break;
  332.                     case '^':
  333.                        
  334.                         AddUnitType(UseOptionM() ? RegexNode.Bol : RegexNode.Beginning);
  335.                         break;
  336.                     case '$':
  337.                        
  338.                         AddUnitType(UseOptionM() ? RegexNode.Eol : RegexNode.EndZ);
  339.                         break;
  340.                     case '.':
  341.                        
  342.                         if (UseOptionS())
  343.                             AddUnitSet(RegexCharClass.AnyClass);
  344.                         else
  345.                             AddUnitNotone('\n');
  346.                         break;
  347.                     case '{':
  348.                     case '*':
  349.                     case '+':
  350.                     case '?':
  351.                        
  352.                         if (Unit() == null)
  353.                             throw MakeException(wasPrevQuantifier ? SR.GetString(SR.NestedQuantify, ch.ToString()) : SR.GetString(SR.QuantifyAfterNothing));
  354.                         MoveLeft();
  355.                         break;
  356.                     default:
  357.                        
  358.                         throw MakeException(SR.GetString(SR.InternalError));
  359.                         break;
  360.                 }
  361.                
  362.                 ScanBlank();
  363.                
  364.                 if (CharsRight() == 0 || !(isQuantifier = IsTrueQuantifier())) {
  365.                     AddConcatenate();
  366.                     goto ContinueOuterScan;
  367.                 }
  368.                
  369.                 ch = MoveRightGetChar();
  370.                
  371.                 // Handle quantifiers
  372.                 while (Unit() != null) {
  373.                     int min;
  374.                     int max;
  375.                     bool lazy;
  376.                    
  377.                     switch (ch) {
  378.                         case '*':
  379.                             min = 0;
  380.                             max = Int32.MaxValue;
  381.                             break;
  382.                         case '?':
  383.                            
  384.                             min = 0;
  385.                             max = 1;
  386.                             break;
  387.                         case '+':
  388.                            
  389.                             min = 1;
  390.                             max = Int32.MaxValue;
  391.                             break;
  392.                         case '{':
  393.                            
  394.                            
  395.                             {
  396.                                 startpos = Textpos();
  397.                                 max = min = ScanDecimal();
  398.                                 if (startpos < Textpos()) {
  399.                                     if (CharsRight() > 0 && RightChar() == ',') {
  400.                                         MoveRight();
  401.                                         if (CharsRight() == 0 || RightChar() == '}')
  402.                                             max = Int32.MaxValue;
  403.                                         else
  404.                                             max = ScanDecimal();
  405.                                     }
  406.                                 }
  407.                                
  408.                                 if (startpos == Textpos() || CharsRight() == 0 || MoveRightGetChar() != '}') {
  409.                                     AddConcatenate();
  410.                                     Textto(startpos - 1);
  411.                                     goto ContinueOuterScan;
  412.                                 }
  413.                             }
  414.                            
  415.                             break;
  416.                         default:
  417.                            
  418.                             throw MakeException(SR.GetString(SR.InternalError));
  419.                             break;
  420.                     }
  421.                    
  422.                     ScanBlank();
  423.                    
  424.                     if (CharsRight() == 0 || RightChar() != '?')
  425.                         lazy = false;
  426.                     else {
  427.                         MoveRight();
  428.                         lazy = true;
  429.                     }
  430.                    
  431.                     if (min > max)
  432.                         throw MakeException(SR.GetString(SR.IllegalRange));
  433.                    
  434.                     AddConcatenate(lazy, min, max);
  435.                 }
  436.                 ContinueOuterScan:
  437.                
  438.                 ;
  439.             }
  440.             BreakOuterScan:
  441.            
  442.             ;
  443.            
  444.             if (!EmptyStack())
  445.                 throw MakeException(SR.GetString(SR.NotEnoughParens));
  446.            
  447.             AddGroup();
  448.            
  449.             return Unit();
  450.         }
  451.        
  452. /*
  453.         * Simple parsing for replacement patterns
  454.         */       
  455.         internal RegexNode ScanReplacement()
  456.         {
  457.             int c;
  458.             int startpos;
  459.            
  460.             _concatenation = new RegexNode(RegexNode.Concatenate, _options);
  461.            
  462.             for (;;) {
  463.                 c = CharsRight();
  464.                 if (c == 0)
  465.                     break;
  466.                
  467.                 startpos = Textpos();
  468.                
  469.                 while (c > 0 && RightChar() != '$') {
  470.                     MoveRight();
  471.                     c--;
  472.                 }
  473.                
  474.                 AddConcatenate(startpos, Textpos() - startpos, true);
  475.                
  476.                 if (c > 0) {
  477.                     if (MoveRightGetChar() == '$')
  478.                         AddUnitNode(ScanDollar());
  479.                     AddConcatenate();
  480.                 }
  481.             }
  482.            
  483.             return _concatenation;
  484.         }
  485.        
  486. /*
  487.         * Scans contents of [] (not including []'s), and converts to a
  488.         * RegexCharClass.
  489.         */       
  490.         internal RegexCharClass ScanCharClass(bool caseInsensitive)
  491.         {
  492.             return ScanCharClass(caseInsensitive, false);
  493.         }
  494.        
  495. /*
  496.         * Scans contents of [] (not including []'s), and converts to a
  497.         * RegexCharClass.
  498.         */       
  499.         internal RegexCharClass ScanCharClass(bool caseInsensitive, bool scanOnly)
  500.         {
  501.             char ch = '\0';
  502.             char chPrev = '\0';
  503.             bool inRange = false;
  504.             bool firstChar = true;
  505.             bool closed = false;
  506.            
  507.             RegexCharClass cc;
  508.            
  509.             cc = scanOnly ? null : new RegexCharClass();
  510.            
  511.             if (CharsRight() > 0 && RightChar() == '^') {
  512.                 MoveRight();
  513.                 if (!scanOnly)
  514.                     cc.Negate = true;
  515.             }
  516.            
  517.             for (; CharsRight() > 0; firstChar = false) {
  518.                 bool fTranslatedChar = false;
  519.                 ch = MoveRightGetChar();
  520.                 if (ch == ']') {
  521.                     if (!firstChar) {
  522.                         closed = true;
  523.                         break;
  524.                     }
  525.                 }
  526.                 else if (ch == '\\' && CharsRight() > 0) {
  527.                    
  528.                     switch (ch = MoveRightGetChar()) {
  529.                         case 'D':
  530.                         case 'd':
  531.                             if (!scanOnly) {
  532.                                 if (inRange)
  533.                                     throw MakeException(SR.GetString(SR.BadClassInCharRange, ch.ToString()));
  534.                                 cc.AddDigit(UseOptionE(), ch == 'D', _pattern);
  535.                             }
  536.                             continue;
  537.                         case 'S':
  538.                         case 's':
  539.                            
  540.                             if (!scanOnly) {
  541.                                 if (inRange)
  542.                                     throw MakeException(SR.GetString(SR.BadClassInCharRange, ch.ToString()));
  543.                                 cc.AddSpace(UseOptionE(), ch == 'S');
  544.                             }
  545.                             continue;
  546.                         case 'W':
  547.                         case 'w':
  548.                            
  549.                             if (!scanOnly) {
  550.                                 if (inRange)
  551.                                     throw MakeException(SR.GetString(SR.BadClassInCharRange, ch.ToString()));
  552.                                
  553.                                 cc.AddWord(UseOptionE(), ch == 'W');
  554.                             }
  555.                             continue;
  556.                         case 'p':
  557.                         case 'P':
  558.                            
  559.                             if (!scanOnly) {
  560.                                 if (inRange)
  561.                                     throw MakeException(SR.GetString(SR.BadClassInCharRange, ch.ToString()));
  562.                                 cc.AddCategoryFromName(ParseProperty(), (ch != 'p'), caseInsensitive, _pattern);
  563.                             }
  564.                             else
  565.                                 ParseProperty();
  566.                            
  567.                             continue;
  568.                         case '-':
  569.                            
  570.                             if (!scanOnly)
  571.                                 cc.AddRange(ch, ch);
  572.                             continue;
  573.                         default:
  574.                            
  575.                             MoveLeft();
  576.                             ch = ScanCharEscape();
  577.                             // non-literal character
  578.                             fTranslatedChar = true;
  579.                             break;
  580.                         // this break will only break out of the switch
  581.                     }
  582.                 }
  583.                 else if (ch == '[') {
  584.                     // This is code for Posix style properties - [:Ll:] or [:IsTibetan:].
  585.                     // It currently doesn't do anything other than skip the whole thing!
  586.                     if (CharsRight() > 0 && RightChar() == ':' && !inRange) {
  587.                         string name;
  588.                         int savePos = Textpos();
  589.                        
  590.                         MoveRight();
  591.                         name = ScanCapname();
  592.                         if (CharsRight() < 2 || MoveRightGetChar() != ':' || MoveRightGetChar() != ']')
  593.                             Textto(savePos);
  594.                         // else lookup name (nyi)
  595.                     }
  596.                 }
  597.                
  598.                
  599.                 if (inRange) {
  600.                     inRange = false;
  601.                     if (!scanOnly) {
  602.                         if (ch == '[' && !fTranslatedChar && !firstChar) {
  603.                             // We thought we were in a range, but we're actually starting a subtraction.
  604.                             // In that case, we'll add chPrev to our char class, skip the opening [, and
  605.                             // scan the new character class recursively.
  606.                             cc.AddChar(chPrev);
  607.                             cc.AddSubtraction(ScanCharClass(caseInsensitive, false));
  608.                            
  609.                             if (CharsRight() > 0 && RightChar() != ']')
  610.                                 throw MakeException(SR.GetString(SR.SubtractionMustBeLast));
  611.                         }
  612.                         else {
  613.                             // a regular range, like a-z
  614.                             if (chPrev > ch)
  615.                                 throw MakeException(SR.GetString(SR.ReversedCharRange));
  616.                             cc.AddRange(chPrev, ch);
  617.                         }
  618.                     }
  619.                 }
  620.                 else if (CharsRight() >= 2 && RightChar() == '-' && RightChar(1) != ']') {
  621.                     // this could be the start of a range
  622.                     chPrev = ch;
  623.                     inRange = true;
  624.                     MoveRight();
  625.                 }
  626.                 else if (CharsRight() >= 1 && ch == '-' && !fTranslatedChar && RightChar() == '[' && !firstChar) {
  627.                     // we aren't in a range, and now there is a subtraction. Usually this happens
  628.                     // only when a subtraction follows a range, like [a-z-[b]]
  629.                     if (!scanOnly) {
  630.                         MoveRight(1);
  631.                         cc.AddSubtraction(ScanCharClass(caseInsensitive, false));
  632.                        
  633.                         if (CharsRight() > 0 && RightChar() != ']')
  634.                             throw MakeException(SR.GetString(SR.SubtractionMustBeLast));
  635.                     }
  636.                     else {
  637.                         MoveRight(1);
  638.                         ScanCharClass(caseInsensitive, true);
  639.                     }
  640.                 }
  641.                 else {
  642.                     if (!scanOnly)
  643.                         cc.AddRange(ch, ch);
  644.                 }
  645.             }
  646.            
  647.             if (!closed)
  648.                 throw MakeException(SR.GetString(SR.UnterminatedBracket));
  649.            
  650.             if (!scanOnly && caseInsensitive)
  651.                 cc.AddLowercase(_culture);
  652.            
  653.             return cc;
  654.         }
  655.        
  656. /*
  657.         * Scans chars following a '(' (not counting the '('), and returns
  658.         * a RegexNode for the type of group scanned, or null if the group
  659.         * simply changed options (?cimsx-cimsx) or was a comment (#...).
  660.         */       
  661.         internal RegexNode ScanGroupOpen()
  662.         {
  663.             char ch = '\0';
  664.             int NodeType;
  665.             char close = '>';
  666.            
  667.            
  668.             // just return a RegexNode if we have:
  669.             // 1. "(" followed by nothing
  670.             // 2. "(x" where x != ?
  671.             // 3. "(?)"
  672.             if (CharsRight() == 0 || RightChar() != '?' || (RightChar() == '?' && (CharsRight() > 1 && RightChar(1) == ')'))) {
  673.                 if (UseOptionN() || _ignoreNextParen) {
  674.                     _ignoreNextParen = false;
  675.                     return new RegexNode(RegexNode.Group, _options);
  676.                 }
  677.                 else
  678.                     return new RegexNode(RegexNode.Capture, _options, _autocap++, -1);
  679.             }
  680.            
  681.             MoveRight();
  682.            
  683.             for (;;) {
  684.                 if (CharsRight() == 0)
  685.                     break;
  686.                
  687.                 switch (ch = MoveRightGetChar()) {
  688.                     case ':':
  689.                         NodeType = RegexNode.Group;
  690.                         break;
  691.                     case '=':
  692.                        
  693.                         _options &= ~(RegexOptions.RightToLeft);
  694.                         NodeType = RegexNode.Require;
  695.                         break;
  696.                     case '!':
  697.                        
  698.                         _options &= ~(RegexOptions.RightToLeft);
  699.                         NodeType = RegexNode.Prevent;
  700.                         break;
  701.                     case '>':
  702.                        
  703.                         NodeType = RegexNode.Greedy;
  704.                         break;
  705.                     case '\'':
  706.                        
  707.                         close = '\'';
  708.                         goto case '<';
  709.                         break;
  710.                     case '<':
  711.                         // fallthrough
  712.                        
  713.                         if (CharsRight() == 0)
  714.                             goto BreakRecognize;
  715.                        
  716.                         switch (ch = MoveRightGetChar()) {
  717.                             case '=':
  718.                                 if (close == '\'')
  719.                                     goto BreakRecognize;
  720.                                
  721.                                 _options |= RegexOptions.RightToLeft;
  722.                                 NodeType = RegexNode.Require;
  723.                                 break;
  724.                             case '!':
  725.                                
  726.                                 if (close == '\'')
  727.                                     goto BreakRecognize;
  728.                                
  729.                                 _options |= RegexOptions.RightToLeft;
  730.                                 NodeType = RegexNode.Prevent;
  731.                                 break;
  732.                             default:
  733.                                
  734.                                 MoveLeft();
  735.                                 int capnum = -1;
  736.                                 int uncapnum = -1;
  737.                                 bool proceed = false;
  738.                                
  739.                                 // grab part before -
  740.                                
  741.                                 if (ch >= '0' && ch <= '9') {
  742.                                     capnum = ScanDecimal();
  743.                                    
  744.                                     if (!IsCaptureSlot(capnum))
  745.                                         capnum = -1;
  746.                                    
  747.                                     // check if we have bogus characters after the number
  748.                                     if (CharsRight() > 0 && !(RightChar() == close || RightChar() == '-'))
  749.                                         throw MakeException(SR.GetString(SR.InvalidGroupName));
  750.                                     if (capnum == 0)
  751.                                         throw MakeException(SR.GetString(SR.CapnumNotZero));
  752.                                 }
  753.                                 else if (RegexCharClass.IsWordChar(ch)) {
  754.                                     string capname = ScanCapname();
  755.                                    
  756.                                     if (IsCaptureName(capname))
  757.                                         capnum = CaptureSlotFromName(capname);
  758.                                    
  759.                                     // check if we have bogus character after the name
  760.                                     if (CharsRight() > 0 && !(RightChar() == close || RightChar() == '-'))
  761.                                         throw MakeException(SR.GetString(SR.InvalidGroupName));
  762.                                 }
  763.                                 else if (ch == '-') {
  764.                                     proceed = true;
  765.                                 }
  766.                                 else {
  767.                                     // bad group name - starts with something other than a word character and isn't a number
  768.                                     throw MakeException(SR.GetString(SR.InvalidGroupName));
  769.                                 }
  770.                                
  771.                                 // grab part after - if any
  772.                                
  773.                                 if ((capnum != -1 || proceed == true) && CharsRight() > 0 && RightChar() == '-') {
  774.                                     MoveRight();
  775.                                     ch = RightChar();
  776.                                    
  777.                                     if (ch >= '0' && ch <= '9') {
  778.                                         uncapnum = ScanDecimal();
  779.                                        
  780.                                         if (!IsCaptureSlot(uncapnum))
  781.                                             throw MakeException(SR.GetString(SR.UndefinedBackref, uncapnum));
  782.                                        
  783.                                         // check if we have bogus characters after the number
  784.                                         if (CharsRight() > 0 && RightChar() != close)
  785.                                             throw MakeException(SR.GetString(SR.InvalidGroupName));
  786.                                     }
  787.                                     else if (RegexCharClass.IsWordChar(ch)) {
  788.                                         string uncapname = ScanCapname();
  789.                                        
  790.                                         if (IsCaptureName(uncapname))
  791.                                             uncapnum = CaptureSlotFromName(uncapname);
  792.                                         else
  793.                                             throw MakeException(SR.GetString(SR.UndefinedNameRef, uncapname));
  794.                                        
  795.                                         // check if we have bogus character after the name
  796.                                         if (CharsRight() > 0 && RightChar() != close)
  797.                                             throw MakeException(SR.GetString(SR.InvalidGroupName));
  798.                                     }
  799.                                     else {
  800.                                         // bad group name - starts with something other than a word character and isn't a number
  801.                                         throw MakeException(SR.GetString(SR.InvalidGroupName));
  802.                                     }
  803.                                 }
  804.                                
  805.                                 // actually make the node
  806.                                
  807.                                 if ((capnum != -1 || uncapnum != -1) && CharsRight() > 0 && MoveRightGetChar() == close) {
  808.                                     return new RegexNode(RegexNode.Capture, _options, capnum, uncapnum);
  809.                                 }
  810.                                 goto BreakRecognize;
  811.                                 break;
  812.                         }
  813.                         break;
  814.                     case '(':
  815.                        
  816.                         // alternation construct (?(...) | )
  817.                        
  818.                         int parenPos = Textpos();
  819.                         if (CharsRight() > 0) {
  820.                             ch = RightChar();
  821.                            
  822.                             // check if the alternation condition is a backref
  823.                             if (ch >= '0' && ch <= '9') {
  824.                                 int capnum = ScanDecimal();
  825.                                 if (CharsRight() > 0 && MoveRightGetChar() == ')') {
  826.                                     if (IsCaptureSlot(capnum))
  827.                                         return new RegexNode(RegexNode.Testref, _options, capnum);
  828.                                     else
  829.                                         throw MakeException(SR.GetString(SR.UndefinedReference, capnum.ToString(CultureInfo.CurrentCulture)));
  830.                                 }
  831.                                 else
  832.                                     throw MakeException(SR.GetString(SR.MalformedReference, capnum.ToString(CultureInfo.CurrentCulture)));
  833.                                
  834.                             }
  835.                             else if (RegexCharClass.IsWordChar(ch)) {
  836.                                 string capname = ScanCapname();
  837.                                
  838.                                 if (IsCaptureName(capname) && CharsRight() > 0 && MoveRightGetChar() == ')')
  839.                                     return new RegexNode(RegexNode.Testref, _options, CaptureSlotFromName(capname));
  840.                             }
  841.                         }
  842.                         // not a backref
  843.                         NodeType = RegexNode.Testgroup;
  844.                         Textto(parenPos - 1);
  845.                         // jump to the start of the parentheses
  846.                         _ignoreNextParen = true;
  847.                         // but make sure we don't try to capture the insides
  848.                         int charsRight = CharsRight();
  849.                         if (charsRight >= 3 && RightChar(1) == '?') {
  850.                             char rightchar2 = RightChar(2);
  851.                             // disallow comments in the condition
  852.                             if (rightchar2 == '#')
  853.                                 throw MakeException(SR.GetString(SR.AlternationCantHaveComment));
  854.                            
  855.                             // disallow named capture group (?<..>..) in the condition
  856.                             if (rightchar2 == '\'')
  857.                                 throw MakeException(SR.GetString(SR.AlternationCantCapture));
  858.                             else {
  859.                                 if (charsRight >= 4 && (rightchar2 == '<' && RightChar(3) != '!' && RightChar(3) != '='))
  860.                                     throw MakeException(SR.GetString(SR.AlternationCantCapture));
  861.                             }
  862.                         }
  863.                        
  864.                         break;
  865.                     default:
  866.                        
  867.                        
  868.                         MoveLeft();
  869.                        
  870.                         NodeType = RegexNode.Group;
  871.                         ScanOptions();
  872.                         if (CharsRight() == 0)
  873.                             goto BreakRecognize;
  874.                        
  875.                         if ((ch = MoveRightGetChar()) == ')')
  876.                             return null;
  877.                        
  878.                         if (ch != ':')
  879.                             goto BreakRecognize;
  880.                         break;
  881.                 }
  882.                
  883.                 return new RegexNode(NodeType, _options);
  884.             }
  885.             BreakRecognize:
  886.            
  887.             ;
  888.             // break Recognize comes here
  889.            
  890.             throw MakeException(SR.GetString(SR.UnrecognizedGrouping));
  891.         }
  892.        
  893. /*
  894.         * Scans whitespace or x-mode comments.
  895.         */       
  896.         internal void ScanBlank()
  897.         {
  898.             if (UseOptionX()) {
  899.                 for (;;) {
  900.                     while (CharsRight() > 0 && IsSpace(RightChar()))
  901.                         MoveRight();
  902.                    
  903.                     if (CharsRight() == 0)
  904.                         break;
  905.                    
  906.                     if (RightChar() == '#') {
  907.                         while (CharsRight() > 0 && RightChar() != '\n')
  908.                             MoveRight();
  909.                     }
  910.                     else if (CharsRight() >= 3 && RightChar(2) == '#' && RightChar(1) == '?' && RightChar() == '(') {
  911.                         while (CharsRight() > 0 && RightChar() != ')')
  912.                             MoveRight();
  913.                         if (CharsRight() == 0)
  914.                             throw MakeException(SR.GetString(SR.UnterminatedComment));
  915.                         MoveRight();
  916.                     }
  917.                     else
  918.                         break;
  919.                 }
  920.             }
  921.             else {
  922.                 for (;;) {
  923.                     if (CharsRight() < 3 || RightChar(2) != '#' || RightChar(1) != '?' || RightChar() != '(')
  924.                         return;
  925.                    
  926.                     while (CharsRight() > 0 && RightChar() != ')')
  927.                         MoveRight();
  928.                     if (CharsRight() == 0)
  929.                         throw MakeException(SR.GetString(SR.UnterminatedComment));
  930.                     MoveRight();
  931.                 }
  932.             }
  933.         }
  934.        
  935. /*
  936.         * Scans chars following a '\' (not counting the '\'), and returns
  937.         * a RegexNode for the type of atom scanned.
  938.         */       
  939.         internal RegexNode ScanBackslash()
  940.         {
  941.             char ch;
  942.             RegexCharClass cc;
  943.            
  944.             if (CharsRight() == 0)
  945.                 throw MakeException(SR.GetString(SR.IllegalEndEscape));
  946.            
  947.             switch (ch = RightChar()) {
  948.                 case 'b':
  949.                 case 'B':
  950.                 case 'A':
  951.                 case 'G':
  952.                 case 'Z':
  953.                 case 'z':
  954.                     MoveRight();
  955.                     return new RegexNode(TypeFromCode(ch), _options);
  956.                 case 'w':
  957.                    
  958.                     MoveRight();
  959.                     if (UseOptionE())
  960.                         return new RegexNode(RegexNode.Set, _options, RegexCharClass.ECMAWordClass);
  961.                     return new RegexNode(RegexNode.Set, _options, RegexCharClass.WordClass);
  962.                 case 'W':
  963.                    
  964.                     MoveRight();
  965.                     if (UseOptionE())
  966.                         return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotECMAWordClass);
  967.                     return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotWordClass);
  968.                 case 's':
  969.                    
  970.                     MoveRight();
  971.                     if (UseOptionE())
  972.                         return new RegexNode(RegexNode.Set, _options, RegexCharClass.ECMASpaceClass);
  973.                     return new RegexNode(RegexNode.Set, _options, RegexCharClass.SpaceClass);
  974.                 case 'S':
  975.                    
  976.                     MoveRight();
  977.                     if (UseOptionE())
  978.                         return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotECMASpaceClass);
  979.                     return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotSpaceClass);
  980.                 case 'd':
  981.                    
  982.                     MoveRight();
  983.                     if (UseOptionE())
  984.                         return new RegexNode(RegexNode.Set, _options, RegexCharClass.ECMADigitClass);
  985.                     return new RegexNode(RegexNode.Set, _options, RegexCharClass.DigitClass);
  986.                 case 'D':
  987.                    
  988.                     MoveRight();
  989.                     if (UseOptionE())
  990.                         return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotECMADigitClass);
  991.                     return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotDigitClass);
  992.                 case 'p':
  993.                 case 'P':
  994.                    
  995.                     MoveRight();
  996.                     cc = new RegexCharClass();
  997.                     cc.AddCategoryFromName(ParseProperty(), (ch != 'p'), UseOptionI(), _pattern);
  998.                     if (UseOptionI())
  999.                         cc.AddLowercase(_culture);
  1000.                    
  1001.                     return new RegexNode(RegexNode.Set, _options, cc.ToStringClass());
  1002.                 default:
  1003.                    
  1004.                     return ScanBasicBackslash();
  1005.             }
  1006.         }
  1007.        
  1008. /*
  1009.         * Scans \-style backreferences and character escapes
  1010.         */       
  1011.         internal RegexNode ScanBasicBackslash()
  1012.         {
  1013.             if (CharsRight() == 0)
  1014.                 throw MakeException(SR.GetString(SR.IllegalEndEscape));
  1015.            
  1016.             char ch;
  1017.             bool angled = false;
  1018.             char close = '\0';
  1019.             int backpos;
  1020.            
  1021.             backpos = Textpos();
  1022.             ch = RightChar();
  1023.            
  1024.             // allow \k<foo> instead of <foo>, which is now deprecated
  1025.            
  1026.             if (ch == 'k') {
  1027.                 if (CharsRight() >= 2) {
  1028.                     MoveRight();
  1029.                     ch = MoveRightGetChar();
  1030.                    
  1031.                     if (ch == '<' || ch == '\'') {
  1032.                         angled = true;
  1033.                         close = (ch == '\'') ? '\'' : '>';
  1034.                     }
  1035.                 }
  1036.                
  1037.                 if (!angled || CharsRight() <= 0)
  1038.                     throw MakeException(SR.GetString(SR.MalformedNameRef));
  1039.                
  1040.                 ch = RightChar();
  1041.             }
  1042.            
  1043.            
  1044.             else if ((ch == '<' || ch == '\'') && CharsRight() > 1) {
  1045.                 angled = true;
  1046.                 close = (ch == '\'') ? '\'' : '>';
  1047.                
  1048.                 MoveRight();
  1049.                 ch = RightChar();
  1050.             }
  1051.            
  1052.             // Try to parse backreference: <1> or <cap>
  1053.            
  1054.             if (angled && ch >= '0' && ch <= '9') {
  1055.                 int capnum = ScanDecimal();
  1056.                
  1057.                 if (CharsRight() > 0 && MoveRightGetChar() == close) {
  1058.                     if (IsCaptureSlot(capnum))
  1059.                         return new RegexNode(RegexNode.Ref, _options, capnum);
  1060.                     else
  1061.                         throw MakeException(SR.GetString(SR.UndefinedBackref, capnum.ToString(CultureInfo.CurrentCulture)));
  1062.                 }
  1063.             }
  1064.            
  1065.             // Try to parse backreference or octal: \1
  1066.            
  1067.             else if (!angled && ch >= '1' && ch <= '9') {
  1068.                 if (UseOptionE()) {
  1069.                     int capnum = -1;
  1070.                     int newcapnum = (int)(ch - '0');
  1071.                     int pos = Textpos() - 1;
  1072.                     while (newcapnum <= _captop) {
  1073.                         if (IsCaptureSlot(newcapnum) && (_caps == null || (int)_caps[newcapnum] < pos))
  1074.                             capnum = newcapnum;
  1075.                         MoveRight();
  1076.                         if (CharsRight() == 0 || (ch = RightChar()) < '0' || ch > '9')
  1077.                             break;
  1078.                         newcapnum = newcapnum * 10 + (int)(ch - '0');
  1079.                     }
  1080.                     if (capnum >= 0)
  1081.                         return new RegexNode(RegexNode.Ref, _options, capnum);
  1082.                 }
  1083.                 else {
  1084.                    
  1085.                     int capnum = ScanDecimal();
  1086.                     if (IsCaptureSlot(capnum))
  1087.                         return new RegexNode(RegexNode.Ref, _options, capnum);
  1088.                     else if (capnum <= 9)
  1089.                         throw MakeException(SR.GetString(SR.UndefinedBackref, capnum.ToString(CultureInfo.CurrentCulture)));
  1090.                 }
  1091.             }
  1092.            
  1093.             else if (angled && RegexCharClass.IsWordChar(ch)) {
  1094.                 string capname = ScanCapname();
  1095.                
  1096.                 if (CharsRight() > 0 && MoveRightGetChar() == close) {
  1097.                     if (IsCaptureName(capname))
  1098.                         return new RegexNode(RegexNode.Ref, _options, CaptureSlotFromName(capname));
  1099.                     else
  1100.                         throw MakeException(SR.GetString(SR.UndefinedNameRef, capname));
  1101.                 }
  1102.             }
  1103.            
  1104.             // Not backreference: must be char code
  1105.            
  1106.             Textto(backpos);
  1107.             ch = ScanCharEscape();
  1108.            
  1109.             if (UseOptionI())
  1110.                 ch = Char.ToLower(ch, _culture);
  1111.            
  1112.             return new RegexNode(RegexNode.One, _options, ch);
  1113.         }
  1114.        
  1115. /*
  1116.         * Scans $ patterns recognized within replacment patterns
  1117.         */       
  1118.         internal RegexNode ScanDollar()
  1119.         {
  1120.             if (CharsRight() == 0)
  1121.                 return new RegexNode(RegexNode.One, _options, '$');
  1122.            
  1123.             char ch = RightChar();
  1124.             bool angled;
  1125.             int backpos = Textpos();
  1126.             int lastEndPos = backpos;
  1127.            
  1128.             // Note angle
  1129.            
  1130.             if (ch == '{' && CharsRight() > 1) {
  1131.                 angled = true;
  1132.                 MoveRight();
  1133.                 ch = RightChar();
  1134.             }
  1135.             else {
  1136.                 angled = false;
  1137.             }
  1138.            
  1139.             // Try to parse backreference: \1 or \{1} or \{cap}
  1140.            
  1141.             if (ch >= '0' && ch <= '9') {
  1142.                 if (!angled && UseOptionE()) {
  1143.                     int capnum = -1;
  1144.                     int newcapnum = (int)(ch - '0');
  1145.                     MoveRight();
  1146.                     if (IsCaptureSlot(newcapnum)) {
  1147.                         capnum = newcapnum;
  1148.                         lastEndPos = Textpos();
  1149.                     }
  1150.                    
  1151.                     while (CharsRight() > 0 && (ch = RightChar()) >= '0' && ch <= '9') {
  1152.                         int digit = (int)(ch - '0');
  1153.                         if (newcapnum > (MaxValueDiv10) || (newcapnum == (MaxValueDiv10) && digit > (MaxValueMod10)))
  1154.                             throw MakeException(SR.GetString(SR.CaptureGroupOutOfRange));
  1155.                        
  1156.                         newcapnum = newcapnum * 10 + digit;
  1157.                        
  1158.                         MoveRight();
  1159.                         if (IsCaptureSlot(newcapnum)) {
  1160.                             capnum = newcapnum;
  1161.                             lastEndPos = Textpos();
  1162.                         }
  1163.                     }
  1164.                     Textto(lastEndPos);
  1165.                     if (capnum >= 0)
  1166.                         return new RegexNode(RegexNode.Ref, _options, capnum);
  1167.                 }
  1168.                 else {
  1169.                     int capnum = ScanDecimal();
  1170.                     if (!angled || CharsRight() > 0 && MoveRightGetChar() == '}') {
  1171.                         if (IsCaptureSlot(capnum))
  1172.                             return new RegexNode(RegexNode.Ref, _options, capnum);
  1173.                     }
  1174.                 }
  1175.             }
  1176.             else if (angled && RegexCharClass.IsWordChar(ch)) {
  1177.                 string capname = ScanCapname();
  1178.                
  1179.                 if (CharsRight() > 0 && MoveRightGetChar() == '}') {
  1180.                     if (IsCaptureName(capname))
  1181.                         return new RegexNode(RegexNode.Ref, _options, CaptureSlotFromName(capname));
  1182.                 }
  1183.             }
  1184.             else if (!angled) {
  1185.                 int capnum = 1;
  1186.                
  1187.                 switch (ch) {
  1188.                     case '$':
  1189.                         MoveRight();
  1190.                         return new RegexNode(RegexNode.One, _options, '$');
  1191.                     case '&':
  1192.                        
  1193.                         capnum = 0;
  1194.                         break;
  1195.                     case '`':
  1196.                        
  1197.                         capnum = RegexReplacement.LeftPortion;
  1198.                         break;
  1199.                     case '\'':
  1200.                        
  1201.                         capnum = RegexReplacement.RightPortion;
  1202.                         break;
  1203.                     case '+':
  1204.                        
  1205.                         capnum = RegexReplacement.LastGroup;
  1206.                         break;
  1207.                     case '_':
  1208.                        
  1209.                         capnum = RegexReplacement.WholeString;
  1210.                         break;
  1211.                 }
  1212.                
  1213.                 if (capnum != 1) {
  1214.                     MoveRight();
  1215.                     return new RegexNode(RegexNode.Ref, _options, capnum);
  1216.                 }
  1217.             }
  1218.            
  1219.             // unrecognized $: literalize
  1220.            
  1221.             Textto(backpos);
  1222.             return new RegexNode(RegexNode.One, _options, '$');
  1223.         }
  1224.        
  1225. /*
  1226.         * Scans a capture name: consumes word chars
  1227.         */       
  1228.         internal string ScanCapname()
  1229.         {
  1230.             int startpos = Textpos();
  1231.            
  1232.             while (CharsRight() > 0) {
  1233.                 if (!RegexCharClass.IsWordChar(MoveRightGetChar())) {
  1234.                     MoveLeft();
  1235.                     break;
  1236.                 }
  1237.             }
  1238.            
  1239.             return _pattern.Substring(startpos, Textpos() - startpos);
  1240.         }
  1241.        
  1242.        
  1243. /*
  1244.         * Scans up to three octal digits (stops before exceeding 0377).
  1245.         */       
  1246.         internal char ScanOctal()
  1247.         {
  1248.             int d;
  1249.             int i;
  1250.             int c;
  1251.            
  1252.             // Consume octal chars only up to 3 digits and value 0377
  1253.            
  1254.             c = 3;
  1255.            
  1256.             if (c > CharsRight())
  1257.                 c = CharsRight();
  1258.            
  1259.             for (i = 0; c > 0 && (uint)(d = RightChar() - '0') <= 7; c -= 1) {
  1260.                 MoveRight();
  1261.                 i *= 8;
  1262.                 i += d;
  1263.                 if (UseOptionE() && i >= 32)
  1264.                     break;
  1265.             }
  1266.            
  1267.             // Octal codes only go up to 255. Any larger and the behavior that Perl follows
  1268.             // is simply to truncate the high bits.
  1269.             i &= 255;
  1270.            
  1271.             return (char)i;
  1272.         }
  1273.        
  1274. /*
  1275.         * Scans any number of decimal digits (pegs value at 2^31-1 if too large)
  1276.         */       
  1277.         internal int ScanDecimal()
  1278.         {
  1279.             int i = 0;
  1280.             int d;
  1281.            
  1282.             while (CharsRight() > 0 && (uint)(d = (char)(RightChar() - '0')) <= 9) {
  1283.                 MoveRight();
  1284.                
  1285.                 if (i > (MaxValueDiv10) || (i == (MaxValueDiv10) && d > (MaxValueMod10)))
  1286.                     throw MakeException(SR.GetString(SR.CaptureGroupOutOfRange));
  1287.                
  1288.                 i *= 10;
  1289.                 i += d;
  1290.             }
  1291.            
  1292.             return i;
  1293.         }
  1294.        
  1295. /*
  1296.         * Scans exactly c hex digits (c=2 for \xFF, c=4 for \uFFFF)
  1297.         */       
  1298.         internal char ScanHex(int c)
  1299.         {
  1300.             int i;
  1301.             int d;
  1302.            
  1303.             i = 0;
  1304.            
  1305.             if (CharsRight() >= c) {
  1306.                 for (; c > 0 && ((d = HexDigit(MoveRightGetChar())) >= 0); c -= 1) {
  1307.                     i *= 16;
  1308.                     i += d;
  1309.                 }
  1310.             }
  1311.            
  1312.             if (c > 0)
  1313.                 throw MakeException(SR.GetString(SR.TooFewHex));
  1314.            
  1315.             return (char)i;
  1316.         }
  1317.        
  1318. /*
  1319.         * Returns n <= 0xF for a hex digit.
  1320.         */       
  1321.         static internal int HexDigit(char ch)
  1322.         {
  1323.             int d;
  1324.            
  1325.             if ((uint)(d = ch - '0') <= 9)
  1326.                 return d;
  1327.            
  1328.             if ((uint)(d = ch - 'a') <= 5)
  1329.                 return d + 10;
  1330.            
  1331.             if ((uint)(d = ch - 'A') <= 5)
  1332.                 return d + 10;
  1333.            
  1334.             return -1;
  1335.         }
  1336.        
  1337. /*
  1338.         * Grabs and converts an ascii control character
  1339.         */       
  1340.         internal char ScanControl()
  1341.         {
  1342.             char ch;
  1343.            
  1344.             if (CharsRight() <= 0)
  1345.                 throw MakeException(SR.GetString(SR.MissingControl));
  1346.            
  1347.             ch = MoveRightGetChar();
  1348.            
  1349.             // \ca interpreted as \cA
  1350.            
  1351.             if (ch >= 'a' && ch <= 'z')
  1352.                 ch = (char)(ch - ('a' - 'A'));
  1353.            
  1354.             if ((ch = (char)(ch - '@')) < ' ')
  1355.                 return ch;
  1356.            
  1357.             throw MakeException(SR.GetString(SR.UnrecognizedControl));
  1358.         }
  1359.        
  1360. /*
  1361.         * Returns true for options allowed only at the top level
  1362.         */       
  1363.         internal bool IsOnlyTopOption(RegexOptions option)
  1364.         {
  1365.             return (option == RegexOptions.RightToLeft || option == RegexOptions.Compiled || option == RegexOptions.CultureInvariant || option == RegexOptions.ECMAScript);
  1366.         }
  1367.        
  1368. /*
  1369.         * Scans cimsx-cimsx option string, stops at the first unrecognized char.
  1370.         */       
  1371.         internal void ScanOptions()
  1372.         {
  1373.             char ch;
  1374.             bool off;
  1375.             RegexOptions option;
  1376.            
  1377.             for (off = false; CharsRight() > 0; MoveRight()) {
  1378.                 ch = RightChar();
  1379.                
  1380.                 if (ch == '-') {
  1381.                     off = true;
  1382.                 }
  1383.                 else if (ch == '+') {
  1384.                     off = false;
  1385.                 }
  1386.                 else {
  1387.                     option = OptionFromCode(ch);
  1388.                     if (option == 0 || IsOnlyTopOption(option))
  1389.                         return;
  1390.                    
  1391.                     if (off)
  1392.                         _options &= ~option;
  1393.                     else
  1394.                         _options |= option;
  1395.                 }
  1396.             }
  1397.         }
  1398.        
  1399. /*
  1400.         * Scans \ code for escape codes that map to single unicode chars.
  1401.         */       
  1402.         internal char ScanCharEscape()
  1403.         {
  1404.             char ch;
  1405.            
  1406.             ch = MoveRightGetChar();
  1407.            
  1408.             if (ch >= '0' && ch <= '7') {
  1409.                 MoveLeft();
  1410.                 return ScanOctal();
  1411.             }
  1412.            
  1413.             switch (ch) {
  1414.                 case 'x':
  1415.                     return ScanHex(2);
  1416.                 case 'u':
  1417.                     return ScanHex(4);
  1418.                 case 'a':
  1419.                     return '\a';
  1420.                 case 'b':
  1421.                     return '\b';
  1422.                 case 'e':
  1423.                     return '\u27';
  1424.                 case 'f':
  1425.                     return '\f';
  1426.                 case 'n':
  1427.                     return '\n';
  1428.                 case 'r':
  1429.                     return '\r';
  1430.                 case 't':
  1431.                     return '\t';
  1432.                 case 'v':
  1433.                     return '\v';
  1434.                 case 'c':
  1435.                     return ScanControl();
  1436.                 default:
  1437.                     if (!UseOptionE() && RegexCharClass.IsWordChar(ch))
  1438.                         throw MakeException(SR.GetString(SR.UnrecognizedEscape, ch.ToString()));
  1439.                     return ch;
  1440.             }
  1441.         }
  1442.        
  1443. /*
  1444.         * Scans X for \p{X} or \P{X}
  1445.         */       
  1446.         internal string ParseProperty()
  1447.         {
  1448.             if (CharsRight() < 3) {
  1449.                 throw MakeException(SR.GetString(SR.IncompleteSlashP));
  1450.             }
  1451.             char ch = MoveRightGetChar();
  1452.             if (ch != '{') {
  1453.                 throw MakeException(SR.GetString(SR.MalformedSlashP));
  1454.             }
  1455.            
  1456.             int startpos = Textpos();
  1457.             while (CharsRight() > 0) {
  1458.                 ch = MoveRightGetChar();
  1459.                 if (!(RegexCharClass.IsWordChar(ch) || ch == '-')) {
  1460.                     MoveLeft();
  1461.                     break;
  1462.                 }
  1463.             }
  1464.             string capname = _pattern.Substring(startpos, Textpos() - startpos);
  1465.            
  1466.             if (CharsRight() == 0 || MoveRightGetChar() != '}')
  1467.                 throw MakeException(SR.GetString(SR.IncompleteSlashP));
  1468.            
  1469.             return capname;
  1470.         }
  1471.        
  1472. /*
  1473.         * Returns ReNode type for zero-length assertions with a \ code.
  1474.         */       
  1475.         internal int TypeFromCode(char ch)
  1476.         {
  1477.             switch (ch) {
  1478.                 case 'b':
  1479.                     return UseOptionE() ? RegexNode.ECMABoundary : RegexNode.Boundary;
  1480.                 case 'B':
  1481.                     return UseOptionE() ? RegexNode.NonECMABoundary : RegexNode.Nonboundary;
  1482.                 case 'A':
  1483.                     return RegexNode.Beginning;
  1484.                 case 'G':
  1485.                     return RegexNode.Start;
  1486.                 case 'Z':
  1487.                     return RegexNode.EndZ;
  1488.                 case 'z':
  1489.                     return RegexNode.End;
  1490.                 default:
  1491.                     return RegexNode.Nothing;
  1492.             }
  1493.         }
  1494.        
  1495. /*
  1496.         * Returns option bit from single-char (?cimsx) code.
  1497.         */       
  1498.         static internal RegexOptions OptionFromCode(char ch)
  1499.         {
  1500.             // case-insensitive
  1501.             if (ch >= 'A' && ch <= 'Z')
  1502.                 ch += (char)('a' - 'A');
  1503.            
  1504.             switch (ch) {
  1505.                 case 'c':
  1506.                     return RegexOptions.Compiled;
  1507.                 case 'i':
  1508.                     return RegexOptions.IgnoreCase;
  1509.                 case 'r':
  1510.                     return RegexOptions.RightToLeft;
  1511.                 case 'm':
  1512.                     return RegexOptions.Multiline;
  1513.                 case 'n':
  1514.                     return RegexOptions.ExplicitCapture;
  1515.                 case 's':
  1516.                     return RegexOptions.Singleline;
  1517.                 case 'x':
  1518.                     return RegexOptions.IgnorePatternWhitespace;
  1519.                 case 'd':
  1520.                     #if DBG
  1521.                     return RegexOptions.Debug;
  1522.                 case 'e':
  1523.                     #endif
  1524.                     return RegexOptions.ECMAScript;
  1525.                 default:
  1526.                     return 0;
  1527.             }
  1528.         }
  1529.        
  1530. /*
  1531.         * a prescanner for deducing the slots used for
  1532.         * captures by doing a partial tokenization of the pattern.
  1533.         */       
  1534.         internal void CountCaptures()
  1535.         {
  1536.             char ch;
  1537.            
  1538.             NoteCaptureSlot(0, 0);
  1539.            
  1540.             _autocap = 1;
  1541.            
  1542.             while (CharsRight() > 0) {
  1543.                 int pos = Textpos();
  1544.                 ch = MoveRightGetChar();
  1545.                 switch (ch) {
  1546.                     case '\\':
  1547.                         if (CharsRight() > 0)
  1548.                             MoveRight();
  1549.                         break;
  1550.                     case '#':
  1551.                        
  1552.                         if (UseOptionX()) {
  1553.                             MoveLeft();
  1554.                             ScanBlank();
  1555.                         }
  1556.                         break;
  1557.                     case '[':
  1558.                        
  1559.                         ScanCharClass(false, true);
  1560.                         break;
  1561.                     case ')':
  1562.                        
  1563.                         if (!EmptyOptionsStack())
  1564.                             PopOptions();
  1565.                         break;
  1566.                     case '(':
  1567.                        
  1568.                         if (CharsRight() >= 2 && RightChar(1) == '#' && RightChar() == '?') {
  1569.                             MoveLeft();
  1570.                             ScanBlank();
  1571.                         }
  1572.                         else {
  1573.                            
  1574.                             PushOptions();
  1575.                             if (CharsRight() > 0 && RightChar() == '?') {
  1576.                                 // we have (?...
  1577.                                 MoveRight();
  1578.                                
  1579.                                 if (CharsRight() > 1 && (RightChar() == '<' || RightChar() == '\'')) {
  1580.                                     // named group: (?<... or (?'...
  1581.                                    
  1582.                                     MoveRight();
  1583.                                     ch = RightChar();
  1584.                                    
  1585.                                     if (ch != '0' && RegexCharClass.IsWordChar(ch)) {
  1586.                                         //if (_ignoreNextParen)
  1587.                                         // throw MakeException(SR.GetString(SR.AlternationCantCapture));
  1588.                                         if (ch >= '1' && ch <= '9')
  1589.                                             NoteCaptureSlot(ScanDecimal(), pos);
  1590.                                         else
  1591.                                             NoteCaptureName(ScanCapname(), pos);
  1592.                                     }
  1593.                                 }
  1594.                                 else {
  1595.                                     // (?...
  1596.                                    
  1597.                                     // get the options if it's an option construct (?cimsx-cimsx...)
  1598.                                     ScanOptions();
  1599.                                    
  1600.                                     if (CharsRight() > 0) {
  1601.                                         if (RightChar() == ')') {
  1602.                                             // (?cimsx-cimsx)
  1603.                                             MoveRight();
  1604.                                             PopKeepOptions();
  1605.                                         }
  1606.                                         else if (RightChar() == '(') {
  1607.                                             // alternation construct: (?(foo)yes|no)
  1608.                                             // ignore the next paren so we don't capture the condition
  1609.                                             _ignoreNextParen = true;
  1610.                                            
  1611.                                             // break from here so we don't reset _ignoreNextParen
  1612.                                             break;
  1613.                                         }
  1614.                                     }
  1615.                                 }
  1616.                             }
  1617.                             else {
  1618.                                 if (!UseOptionN() && !_ignoreNextParen)
  1619.                                     NoteCaptureSlot(_autocap++, pos);
  1620.                             }
  1621.                         }
  1622.                        
  1623.                         _ignoreNextParen = false;
  1624.                         break;
  1625.                 }
  1626.             }
  1627.            
  1628.             AssignNameSlots();
  1629.         }
  1630.        
  1631. /*
  1632.         * Notes a used capture slot
  1633.         */       
  1634.         internal void NoteCaptureSlot(int i, int pos)
  1635.         {
  1636.             if (!_caps.ContainsKey(i)) {
  1637.                 // the rhs of the hashtable isn't used in the parser
  1638.                
  1639.                 _caps.Add(i, pos);
  1640.                 _capcount++;
  1641.                
  1642.                 if (_captop <= i) {
  1643.                     if (i == Int32.MaxValue)
  1644.                         _captop = i;
  1645.                     else
  1646.                         _captop = i + 1;
  1647.                 }
  1648.             }
  1649.         }
  1650.        
  1651. /*
  1652.         * Notes a used capture slot
  1653.         */       
  1654.         internal void NoteCaptureName(string name, int pos)
  1655.         {
  1656.             if (_capnames == null) {
  1657.                 _capnames = new Hashtable();
  1658.                 _capnamelist = new ArrayList();
  1659.             }
  1660.            
  1661.             if (!_capnames.ContainsKey(name)) {
  1662.                 _capnames.Add(name, pos);
  1663.                 _capnamelist.Add(name);
  1664.             }
  1665.         }
  1666.        
  1667. /*
  1668.         * For when all the used captures are known: note them all at once
  1669.         */       
  1670.         internal void NoteCaptures(Hashtable caps, int capsize, Hashtable capnames)
  1671.         {
  1672.             _caps = caps;
  1673.             _capsize = capsize;
  1674.             _capnames = capnames;
  1675.         }
  1676.        
  1677. /*
  1678.         * Assigns unused slot numbers to the capture names
  1679.         */       
  1680.         internal void AssignNameSlots()
  1681.         {
  1682.             if (_capnames != null) {
  1683.                 for (int i = 0; i < _capnamelist.Count; i++) {
  1684.                     while (IsCaptureSlot(_autocap))
  1685.                         _autocap++;
  1686.                     string name = (string)_capnamelist[i];
  1687.                     int pos = (int)_capnames[name];
  1688.                     _capnames[name] = _autocap;
  1689.                     NoteCaptureSlot(_autocap, pos);
  1690.                    
  1691.                     _autocap++;
  1692.                 }
  1693.             }
  1694.            
  1695.             // if the caps array has at least one gap, construct the list of used slots
  1696.            
  1697.             if (_capcount < _captop) {
  1698.                 _capnumlist = new object[_capcount];
  1699.                 int i = 0;
  1700.                
  1701.                 for (IDictionaryEnumerator de = _caps.GetEnumerator(); de.MoveNext();)
  1702.                     _capnumlist[i++] = de.Key;
  1703.                
  1704.                 System.Array.Sort(_capnumlist, InvariantComparer.Default);
  1705.             }
  1706.            
  1707.             // merge capsnumlist into capnamelist
  1708.            
  1709.             if (_capnames != null || _capnumlist != null) {
  1710.                 ArrayList oldcapnamelist;
  1711.                 int next;
  1712.                 int k = 0;
  1713.                
  1714.                 if (_capnames == null) {
  1715.                     oldcapnamelist = null;
  1716.                     _capnames = new Hashtable();
  1717.                     _capnamelist = new ArrayList();
  1718.                     next = -1;
  1719.                 }
  1720.                 else {
  1721.                     oldcapnamelist = _capnamelist;
  1722.                     _capnamelist = new ArrayList();
  1723.                     next = (int)_capnames[oldcapnamelist[0]];
  1724.                 }
  1725.                
  1726.                 for (int i = 0; i < _capcount; i++) {
  1727.                     int j = (_capnumlist == null) ? i : (int)_capnumlist[i];
  1728.                    
  1729.                     if (next == j) {
  1730.                         _capnamelist.Add((string)oldcapnamelist[k++]);
  1731.                         next = (k == oldcapnamelist.Count) ? -1 : (int)_capnames[oldcapnamelist[k]];
  1732.                     }
  1733.                     else {
  1734.                         string str = Convert.ToString(j, _culture);
  1735.                         _capnamelist.Add(str);
  1736.                         _capnames[str] = j;
  1737.                     }
  1738.                 }
  1739.             }
  1740.         }
  1741.        
  1742. /*
  1743.         * Looks up the slot number for a given name
  1744.         */       
  1745.         internal int CaptureSlotFromName(string capname)
  1746.         {
  1747.             return (int)_capnames[capname];
  1748.         }
  1749.        
  1750. /*
  1751.         * True if the capture slot was noted
  1752.         */       
  1753.         internal bool IsCaptureSlot(int i)
  1754.         {
  1755.             if (_caps != null)
  1756.                 return _caps.ContainsKey(i);
  1757.            
  1758.             return (i >= 0 && i < _capsize);
  1759.         }
  1760.        
  1761. /*
  1762.         * Looks up the slot number for a given name
  1763.         */       
  1764.         internal bool IsCaptureName(string capname)
  1765.         {
  1766.             if (_capnames == null)
  1767.                 return false;
  1768.            
  1769.             return _capnames.ContainsKey(capname);
  1770.         }
  1771.        
  1772. /*
  1773.         * True if N option disabling '(' autocapture is on.
  1774.         */       
  1775.         internal bool UseOptionN()
  1776.         {
  1777.             return (_options & RegexOptions.ExplicitCapture) != 0;
  1778.         }
  1779.        
  1780. /*
  1781.         * True if I option enabling case-insensitivity is on.
  1782.         */       
  1783.         internal bool UseOptionI()
  1784.         {
  1785.             return (_options & RegexOptions.IgnoreCase) != 0;
  1786.         }
  1787.        
  1788. /*
  1789.         * True if M option altering meaning of $ and ^ is on.
  1790.         */       
  1791.         internal bool UseOptionM()
  1792.         {
  1793.             return (_options & RegexOptions.Multiline) != 0;
  1794.         }
  1795.        
  1796. /*
  1797.         * True if S option altering meaning of . is on.
  1798.         */       
  1799.         internal bool UseOptionS()
  1800.         {
  1801.             return (_options & RegexOptions.Singleline) != 0;
  1802.         }
  1803.        
  1804. /*
  1805.         * True if X option enabling whitespace/comment mode is on.
  1806.         */       
  1807.         internal bool UseOptionX()
  1808.         {
  1809.             return (_options & RegexOptions.IgnorePatternWhitespace) != 0;
  1810.         }
  1811.        
  1812. /*
  1813.         * True if E option enabling ECMAScript behavior is on.
  1814.         */       
  1815.         internal bool UseOptionE()
  1816.         {
  1817.             return (_options & RegexOptions.ECMAScript) != 0;
  1818.         }
  1819.        
  1820.         internal const byte Q = 5;
  1821.         // quantifier
  1822.         internal const byte S = 4;
  1823.         // ordinary stoppper
  1824.         internal const byte Z = 3;
  1825.         // ScanBlank stopper
  1826.         internal const byte X = 2;
  1827.         // whitespace
  1828.         internal const byte E = 1;
  1829.         // should be escaped
  1830. /*
  1831.         * For categorizing ascii characters.
  1832.         */       
  1833.         static internal readonly byte[] _category = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, X,
  1834.         X, 0, X, X, 0, 0, 0, 0, 0, 0,
  1835.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1836.         0, 0, X, 0, 0, Z, S, 0, 0, 0,
  1837.         S, S, Q, Q, 0, 0, S, 0, 0, 0,
  1838.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1839.         0, 0, 0, Q, 0, 0, 0, 0, 0, 0,
  1840.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1841.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1842.         0, S, S, 0, S, 0, 0, 0, 0, 0,
  1843.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1844.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1845.             // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F
  1846.             // ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
  1847.             // @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
  1848.             // ' a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
  1849.         0, 0, 0, Q, S, 0, 0, 0};
  1850.        
  1851. /*
  1852.         * Returns true for those characters that terminate a string of ordinary chars.
  1853.         */       
  1854.         static internal bool IsSpecial(char ch)
  1855.         {
  1856.             return (ch <= '|' && _category[ch] >= S);
  1857.         }
  1858.        
  1859. /*
  1860.         * Returns true for those characters that terminate a string of ordinary chars.
  1861.         */       
  1862.         static internal bool IsStopperX(char ch)
  1863.         {
  1864.             return (ch <= '|' && _category[ch] >= X);
  1865.         }
  1866.        
  1867. /*
  1868.         * Returns true for those characters that begin a quantifier.
  1869.         */       
  1870.         static internal bool IsQuantifier(char ch)
  1871.         {
  1872.             return (ch <= '{' && _category[ch] >= Q);
  1873.         }
  1874.        
  1875.         internal bool IsTrueQuantifier()
  1876.         {
  1877.             int nChars = CharsRight();
  1878.             if (nChars == 0)
  1879.                 return false;
  1880.             int startpos = Textpos();
  1881.             char ch = CharAt(startpos);
  1882.             if (ch != '{')
  1883.                 return ch <= '{' && _category[ch] >= Q;
  1884.             int pos = startpos;
  1885.             while (--nChars > 0 && (ch = CharAt(++pos)) >= '0' && ch <= '9')
  1886.                 ;
  1887.             if (nChars == 0 || pos - startpos == 1)
  1888.                 return false;
  1889.             if (ch == '}')
  1890.                 return true;
  1891.             if (ch != ',')
  1892.                 return false;
  1893.             while (--nChars > 0 && (ch = CharAt(++pos)) >= '0' && ch <= '9')
  1894.                 ;
  1895.             return nChars > 0 && ch == '}';
  1896.         }
  1897.        
  1898. /*
  1899.         * Returns true for whitespace.
  1900.         */       
  1901.         static internal bool IsSpace(char ch)
  1902.         {
  1903.             return (ch <= ' ' && _category[ch] == X);
  1904.         }
  1905.        
  1906. /*
  1907.         * Returns true for chars that should be escaped.
  1908.         */       
  1909.         static internal bool IsMetachar(char ch)
  1910.         {
  1911.             return (ch <= '|' && _category[ch] >= E);
  1912.         }
  1913.        
  1914.        
  1915. /*
  1916.         * Add a string to the last concatenate.
  1917.         */       
  1918.         internal void AddConcatenate(int pos, int cch, bool isReplacement)
  1919.         {
  1920.             RegexNode node;
  1921.            
  1922.             if (cch == 0)
  1923.                 return;
  1924.            
  1925.             if (cch > 1) {
  1926.                 string str = _pattern.Substring(pos, cch);
  1927.                
  1928.                 if (UseOptionI() && !isReplacement) {
  1929.                     // We do the ToLower character by character for consistency. With surrogate chars, doing
  1930.                     // a ToLower on the entire string could actually change the surrogate pair. This is more correct
  1931.                     // linguistically, but since Regex doesn't support surrogates, it's more important to be
  1932.                     // consistent.
  1933.                     StringBuilder sb = new StringBuilder(str.Length);
  1934.                     for (int i = 0; i < str.Length; i++)
  1935.                         sb.Append(Char.ToLower(str[i], _culture));
  1936.                     str = sb.ToString();
  1937.                 }
  1938.                
  1939.                 node = new RegexNode(RegexNode.Multi, _options, str);
  1940.             }
  1941.             else {
  1942.                 char ch = _pattern[pos];
  1943.                
  1944.                 if (UseOptionI() && !isReplacement)
  1945.                     ch = Char.ToLower(ch, _culture);
  1946.                
  1947.                 node = new RegexNode(RegexNode.One, _options, ch);
  1948.             }
  1949.            
  1950.             _concatenation.AddChild(node);
  1951.         }
  1952.        
  1953. /*
  1954.         * Push the parser state (in response to an open paren)
  1955.         */       
  1956.         internal void PushGroup()
  1957.         {
  1958.             _group._next = _stack;
  1959.             _alternation._next = _group;
  1960.             _concatenation._next = _alternation;
  1961.             _stack = _concatenation;
  1962.         }
  1963.        
  1964. /*
  1965.         * Remember the pushed state (in response to a ')')
  1966.         */       
  1967.         internal void PopGroup()
  1968.         {
  1969.             _concatenation = _stack;
  1970.             _alternation = _concatenation._next;
  1971.             _group = _alternation._next;
  1972.             _stack = _group._next;
  1973.            
  1974.             // The first () inside a Testgroup group goes directly to the group
  1975.             if (_group.Type() == RegexNode.Testgroup && _group.ChildCount() == 0) {
  1976.                 if (_unit == null)
  1977.                     throw MakeException(SR.GetString(SR.IllegalCondition));
  1978.                
  1979.                 _group.AddChild(_unit);
  1980.                 _unit = null;
  1981.             }
  1982.         }
  1983.        
  1984. /*
  1985.         * True if the group stack is empty.
  1986.         */       
  1987.         internal bool EmptyStack()
  1988.         {
  1989.             return _stack == null;
  1990.         }
  1991.        
  1992. /*
  1993.         * Start a new round for the parser state (in response to an open paren or string start)
  1994.         */       
  1995.         internal void StartGroup(RegexNode openGroup)
  1996.         {
  1997.             _group = openGroup;
  1998.             _alternation = new RegexNode(RegexNode.Alternate, _options);
  1999.             _concatenation = new RegexNode(RegexNode.Concatenate, _options);
  2000.         }
  2001.        
  2002. /*
  2003.         * Finish the current concatenation (in response to a |)
  2004.         */       
  2005.         internal void AddAlternate()
  2006.         {
  2007.             // The | parts inside a Testgroup group go directly to the group
  2008.            
  2009.             if (_group.Type() == RegexNode.Testgroup || _group.Type() == RegexNode.Testref) {
  2010.                 _group.AddChild(_concatenation.ReverseLeft());
  2011.             }
  2012.             else {
  2013.                 _alternation.AddChild(_concatenation.ReverseLeft());
  2014.             }
  2015.            
  2016.             _concatenation = new RegexNode(RegexNode.Concatenate, _options);
  2017.         }
  2018.        
  2019. /*
  2020.         * Finish the current quantifiable (when a quantifier is not found or is not possible)
  2021.         */       
  2022.         internal void AddConcatenate()
  2023.         {
  2024.             // The first (| inside a Testgroup group goes directly to the group
  2025.            
  2026.             _concatenation.AddChild(_unit);
  2027.             _unit = null;
  2028.         }
  2029.        
  2030. /*
  2031.         * Finish the current quantifiable (when a quantifier is found)
  2032.         */       
  2033.         internal void AddConcatenate(bool lazy, int min, int max)
  2034.         {
  2035.             _concatenation.AddChild(_unit.MakeQuantifier(lazy, min, max));
  2036.             _unit = null;
  2037.         }
  2038.        
  2039. /*
  2040.         * Returns the current unit
  2041.         */       
  2042.         internal RegexNode Unit()
  2043.         {
  2044.             return _unit;
  2045.         }
  2046.        
  2047. /*
  2048.         * Sets the current unit to a single char node
  2049.         */       
  2050.         internal void AddUnitOne(char ch)
  2051.         {
  2052.             if (UseOptionI())
  2053.                 ch = Char.ToLower(ch, _culture);
  2054.            
  2055.             _unit = new RegexNode(RegexNode.One, _options, ch);
  2056.         }
  2057.        
  2058. /*
  2059.         * Sets the current unit to a single inverse-char node
  2060.         */       
  2061.         internal void AddUnitNotone(char ch)
  2062.         {
  2063.             if (UseOptionI())
  2064.                 ch = Char.ToLower(ch, _culture);
  2065.            
  2066.             _unit = new RegexNode(RegexNode.Notone, _options, ch);
  2067.         }
  2068.        
  2069. /*
  2070.         * Sets the current unit to a single set node
  2071.         */       
  2072.         internal void AddUnitSet(string cc)
  2073.         {
  2074.             _unit = new RegexNode(RegexNode.Set, _options, cc);
  2075.         }
  2076.        
  2077. /*
  2078.         * Sets the current unit to a subtree
  2079.         */       
  2080.         internal void AddUnitNode(RegexNode node)
  2081.         {
  2082.             _unit = node;
  2083.         }
  2084.        
  2085. /*
  2086.         * Sets the current unit to an assertion of the specified type
  2087.         */       
  2088.         internal void AddUnitType(int type)
  2089.         {
  2090.             _unit = new RegexNode(type, _options);
  2091.         }
  2092.        
  2093. /*
  2094.         * Finish the current group (in response to a ')' or end)
  2095.         */       
  2096.         internal void AddGroup()
  2097.         {
  2098.             if (_group.Type() == RegexNode.Testgroup || _group.Type() == RegexNode.Testref) {
  2099.                 _group.AddChild(_concatenation.ReverseLeft());
  2100.                
  2101.                 if (_group.Type() == RegexNode.Testref && _group.ChildCount() > 2 || _group.ChildCount() > 3)
  2102.                     throw MakeException(SR.GetString(SR.TooManyAlternates));
  2103.             }
  2104.             else {
  2105.                 _alternation.AddChild(_concatenation.ReverseLeft());
  2106.                 _group.AddChild(_alternation);
  2107.             }
  2108.            
  2109.             _unit = _group;
  2110.         }
  2111.        
  2112. /*
  2113.         * Saves options on a stack.
  2114.         */       
  2115.         internal void PushOptions()
  2116.         {
  2117.             _optionsStack.Add(_options);
  2118.         }
  2119.        
  2120. /*
  2121.         * Recalls options from the stack.
  2122.         */       
  2123.         internal void PopOptions()
  2124.         {
  2125.             _options = (RegexOptions)_optionsStack[_optionsStack.Count - 1];
  2126.             _optionsStack.RemoveAt(_optionsStack.Count - 1);
  2127.         }
  2128.        
  2129. /*
  2130.         * True if options stack is empty.
  2131.         */       
  2132.         internal bool EmptyOptionsStack()
  2133.         {
  2134.             return (_optionsStack.Count == 0);
  2135.         }
  2136.        
  2137. /*
  2138.         * Pops the option stack, but keeps the current options unchanged.
  2139.         */       
  2140.         internal void PopKeepOptions()
  2141.         {
  2142.             _optionsStack.RemoveAt(_optionsStack.Count - 1);
  2143.         }
  2144.        
  2145. /*
  2146.         * Fills in an ArgumentException
  2147.         */       
  2148.         internal ArgumentException MakeException(string message)
  2149.         {
  2150.             return new ArgumentException(SR.GetString(SR.MakeException, _pattern, message));
  2151.         }
  2152.        
  2153. /*
  2154.         * Returns the current parsing position.
  2155.         */       
  2156.         internal int Textpos()
  2157.         {
  2158.             return _currentPos;
  2159.         }
  2160.        
  2161. /*
  2162.         * Zaps to a specific parsing position.
  2163.         */       
  2164.         internal void Textto(int pos)
  2165.         {
  2166.             _currentPos = pos;
  2167.         }
  2168.        
  2169. /*
  2170.         * Returns the char at the right of the current parsing position and advances to the right.
  2171.         */       
  2172.         internal char MoveRightGetChar()
  2173.         {
  2174.             return _pattern[_currentPos++];
  2175.         }
  2176.        
  2177. /*
  2178.         * Moves the current position to the right.
  2179.         */       
  2180.         internal void MoveRight()
  2181.         {
  2182.             MoveRight(1);
  2183.         }
  2184.        
  2185.         internal void MoveRight(int i)
  2186.         {
  2187.             _currentPos += i;
  2188.         }
  2189.        
  2190. /*
  2191.         * Moves the current parsing position one to the left.
  2192.         */       
  2193.         internal void MoveLeft()
  2194.         {
  2195.             --_currentPos;
  2196.         }
  2197.        
  2198. /*
  2199.         * Returns the char left of the current parsing position.
  2200.         */       
  2201.         internal char CharAt(int i)
  2202.         {
  2203.             return _pattern[i];
  2204.         }
  2205.        
  2206. /*
  2207.         * Returns the char right of the current parsing position.
  2208.         */       
  2209.         internal char RightChar()
  2210.         {
  2211.             return _pattern[_currentPos];
  2212.         }
  2213.        
  2214. /*
  2215.         * Returns the char i chars right of the current parsing position.
  2216.         */       
  2217.         internal char RightChar(int i)
  2218.         {
  2219.             return _pattern[_currentPos + i];
  2220.         }
  2221.        
  2222. /*
  2223.         * Number of characters to the right of the current parsing position.
  2224.         */       
  2225.         internal int CharsRight()
  2226.         {
  2227.             return _pattern.Length - _currentPos;
  2228.         }
  2229.     }
  2230. }

Developer Fusion