File: regex\system\text\regularexpressions\RegexParser.cs
Project: ndp\fx\src\System.csproj (System)
//------------------------------------------------------------------------------
// <copyright file="RegexParser.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
// This RegexParser class is internal to the Regex package.
// It builds a tree of RegexNodes from a regular expression
 
// Implementation notes:
//
// It would be nice to get rid of the comment modes, since the
// ScanBlank() calls are just kind of duct-taped in.
 
 
namespace System.Text.RegularExpressions {
 
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;
        
    internal sealed class RegexParser {
        internal RegexNode _stack;
        internal RegexNode _group;
        internal RegexNode _alternation;
        internal RegexNode _concatenation;
        internal RegexNode _unit;
 
        internal String _pattern;
        internal int _currentPos;
        internal CultureInfo _culture;
        
        internal int _autocap;
        internal int _capcount;
        internal int _captop;
        internal int _capsize;
#if SILVERLIGHT
        internal Dictionary<Int32, Int32> _caps;
        internal Dictionary<String, Int32> _capnames;
#else
        internal Hashtable _caps;
        internal Hashtable _capnames;
#endif
        internal Int32[] _capnumlist;
        internal List<String> _capnamelist;
 
        internal RegexOptions _options;
        internal List<RegexOptions> _optionsStack;
 
        internal bool _ignoreNextParen = false;
        
        internal const int MaxValueDiv10 = Int32.MaxValue / 10;
        internal const int MaxValueMod10 = Int32.MaxValue % 10;
 
        /*
         * This static call constructs a RegexTree from a regular expression
         * pattern string and an option string.
         *
         * The method creates, drives, and drops a parser instance.
         */
        internal static RegexTree Parse(String re, RegexOptions op) {
            RegexParser p;
            RegexNode root;
            String[] capnamelist;
 
            p = new RegexParser((op & RegexOptions.CultureInvariant) != 0 ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
 
            p._options = op;
 
            p.SetPattern(re);
            p.CountCaptures();
            p.Reset(op);
            root = p.ScanRegex();
 
            if (p._capnamelist == null)
                capnamelist = null;
            else
                capnamelist = p._capnamelist.ToArray();
 
            return new RegexTree(root, p._caps, p._capnumlist, p._captop, p._capnames, capnamelist, op);
        }
 
        /*
         * This static call constructs a flat concatenation node given
         * a replacement pattern.
         */
#if SILVERLIGHT
        internal static RegexReplacement ParseReplacement(String rep, Dictionary<Int32, Int32> caps, int capsize, Dictionary<String, Int32> capnames, RegexOptions op) {
#else
        internal static RegexReplacement ParseReplacement(String rep, Hashtable caps, int capsize, Hashtable capnames, RegexOptions op) {
#endif
            RegexParser p;
            RegexNode root;
 
            p = new RegexParser((op & RegexOptions.CultureInvariant) != 0 ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
 
            p._options = op;
 
            p.NoteCaptures(caps, capsize, capnames);
            p.SetPattern(rep);
            root = p.ScanReplacement();
 
            return new RegexReplacement(rep, root, caps);
        }
 
        /*
         * Escapes all metacharacters (including |,(,),[,{,|,^,$,*,+,?,\, spaces and #)
         */
        internal static String Escape(String input) {
            for (int i = 0; i < input.Length; i++) {
                if (IsMetachar(input[i])) {
                    StringBuilder sb = new StringBuilder();
                    char ch = input[i];
                    int lastpos;
 
                    sb.Append(input, 0, i);
                    do {
                        sb.Append('\\');
                        switch (ch) {
                            case '\n':
                                ch = 'n';
                                break;
                            case '\r':
                                ch = 'r';
                                break;
                            case '\t':
                                ch = 't';
                                break;
                            case '\f':
                                ch = 'f';
                                break;
                        }
                        sb.Append(ch);
                        i++;
                        lastpos = i;
 
                        while (i < input.Length) {
                            ch = input[i];
                            if (IsMetachar(ch))
                                break;
 
                            i++;
                        }
 
                        sb.Append(input, lastpos, i - lastpos);
 
                    } while (i < input.Length);
 
                    return sb.ToString();
                }
            }
 
            return input;
        }
 
        /*
         * Escapes all metacharacters (including (,),[,],{,},|,^,$,*,+,?,\, spaces and #)
         */
        internal static String Unescape(String input) {
            for (int i = 0; i < input.Length; i++) {
                if (input[i] == '\\') {
                    StringBuilder sb = new StringBuilder();
                    RegexParser p = new RegexParser(CultureInfo.InvariantCulture);
                    int lastpos;
                    p.SetPattern(input);
 
                    sb.Append(input, 0, i);
                    do {
                        i++;
                        p.Textto(i);
                        if (i < input.Length)
                            sb.Append(p.ScanCharEscape());
                        i = p.Textpos();
                        lastpos = i;
                        while (i < input.Length && input[i] != '\\')
                            i++;
                        sb.Append(input, lastpos, i - lastpos);
 
                    } while (i < input.Length);
 
                    return sb.ToString();
                }
            }
 
            return input;
        }
 
        /*
         * Private constructor.
         */
        private RegexParser(CultureInfo culture) {
            _culture = culture;
            _optionsStack = new List<RegexOptions>();
#if SILVERLIGHT
            _caps = new Dictionary<Int32,Int32>();
#else
            _caps = new Hashtable();
#endif
 
        }
 
        /*
         * Drops a string into the pattern buffer.
         */
        internal void SetPattern(String Re) {
            if (Re == null)
                Re = String.Empty;
            _pattern = Re;
            _currentPos = 0;
        }
 
        /*
         * Resets parsing to the beginning of the pattern.
         */
        internal void Reset(RegexOptions topopts) {
            _currentPos = 0;
            _autocap = 1;
            _ignoreNextParen = false;
 
            if (_optionsStack.Count > 0)
                _optionsStack.RemoveRange(0, _optionsStack.Count - 1);
 
            _options = topopts;
            _stack = null;
        }
 
        /*
         * The main parsing function.
         */
        internal RegexNode ScanRegex() {
            char ch = '@'; // nonspecial ch, means at beginning
            bool isQuantifier = false;
 
            StartGroup(new RegexNode(RegexNode.Capture, _options, 0, -1));
 
            while (CharsRight() > 0) {
                bool wasPrevQuantifier = isQuantifier;
                isQuantifier = false;
 
                ScanBlank();
 
                int startpos = Textpos();
 
                // move past all of the normal characters.  We'll stop when we hit some kind of control character, 
                // or if IgnorePatternWhiteSpace is on, we'll stop when we see some whitespace. 
                if (UseOptionX())
                    while (CharsRight() > 0 && (!IsStopperX(ch = RightChar()) || ch == '{' && !IsTrueQuantifier()))
                        MoveRight();
                else
                    while (CharsRight() > 0 && (!IsSpecial(ch = RightChar()) || ch == '{' && !IsTrueQuantifier()))
                        MoveRight();
 
                int endpos = Textpos();
 
                ScanBlank();
 
                if (CharsRight() == 0)
                    ch = '!'; // nonspecial, means at end
                else if (IsSpecial(ch = RightChar())) {
                    isQuantifier = IsQuantifier(ch);
                    MoveRight();
                } else
                    ch = ' '; // nonspecial, means at ordinary char
 
                if (startpos < endpos) {
                    int cchUnquantified = endpos - startpos - (isQuantifier ? 1 : 0);
 
                    wasPrevQuantifier = false;
 
                    if (cchUnquantified > 0)
                        AddConcatenate(startpos, cchUnquantified, false);
 
                    if (isQuantifier)
                        AddUnitOne(CharAt(endpos - 1));
                }
 
                switch (ch) {
                    case '!':
                        goto BreakOuterScan;
 
                    case ' ':
                        goto ContinueOuterScan;
 
                    case '[':
                        AddUnitSet(ScanCharClass(UseOptionI()).ToStringClass());
                        break;
 
                    case '(': {
                            RegexNode grouper;
 
                            PushOptions();
 
                            if (null == (grouper = ScanGroupOpen())) {
                                PopKeepOptions();
                            }
                            else {
                                PushGroup();
                                StartGroup(grouper);
                            }
                        }
                        continue;
 
                    case '|':
                        AddAlternate();
                        goto ContinueOuterScan;
 
                    case ')':
                        if (EmptyStack())
                            throw MakeException(SR.GetString(SR.TooManyParens));
 
                        AddGroup();
                        PopGroup();
                        PopOptions();
 
                        if (Unit() == null)
                            goto ContinueOuterScan;
                        break;
 
                    case '\\':
                        AddUnitNode(ScanBackslash());
                        break;
 
                    case '^':
                        AddUnitType(UseOptionM() ? RegexNode.Bol : RegexNode.Beginning);
                        break;
 
                    case '$':
                        AddUnitType(UseOptionM() ? RegexNode.Eol : RegexNode.EndZ);
                        break;
 
                    case '.':
                        if (UseOptionS())
                            AddUnitSet(RegexCharClass.AnyClass);
                        else
                            AddUnitNotone('\n');
                        break;
 
                    case '{':
                    case '*':
                    case '+':
                    case '?':
                        if (Unit() == null)
                            throw MakeException(wasPrevQuantifier ?
                                                SR.GetString(SR.NestedQuantify, ch.ToString()) :
                                                SR.GetString(SR.QuantifyAfterNothing));
                        MoveLeft();
                        break;
 
                    default:
                        throw MakeException(SR.GetString(SR.InternalError));
                }
 
                ScanBlank();
 
                if (CharsRight() == 0 || !(isQuantifier = IsTrueQuantifier())) {
                    AddConcatenate();
                    goto ContinueOuterScan;
                }
 
                ch = MoveRightGetChar();
 
                // Handle quantifiers
                while (Unit() != null) {
                    int min;
                    int max;
                    bool lazy;
 
                    switch (ch) {
                        case '*':
                            min = 0;
                            max = Int32.MaxValue;
                            break;
 
                        case '?':
                            min = 0;
                            max = 1;
                            break;
 
                        case '+':
                            min = 1;
                            max = Int32.MaxValue;
                            break;
 
                        case '{': {
                                startpos = Textpos();
                                max = min = ScanDecimal();
                                if (startpos < Textpos()) {
                                    if (CharsRight() > 0 && RightChar() == ',') {
                                        MoveRight();
                                        if (CharsRight() == 0 || RightChar() == '}')
                                            max = Int32.MaxValue;
                                        else
                                            max = ScanDecimal();
                                    }
                                }
 
                                if (startpos == Textpos() || CharsRight() == 0 || MoveRightGetChar() != '}') {
                                    AddConcatenate();
                                    Textto(startpos - 1);
                                    goto ContinueOuterScan;
                                }
                            }
 
                            break;
 
                        default:
                            throw MakeException(SR.GetString(SR.InternalError));
                    }
 
                    ScanBlank();
 
                    if (CharsRight() == 0 || RightChar() != '?')
                        lazy = false;
                    else {
                        MoveRight();
                        lazy = true;
                    }
 
                    if (min > max)
                        throw MakeException(SR.GetString(SR.IllegalRange));
 
                    AddConcatenate(lazy, min, max);
                }
 
                ContinueOuterScan:
                ;
            }
 
            BreakOuterScan: 
            ;
 
            if (!EmptyStack())
                throw MakeException(SR.GetString(SR.NotEnoughParens));
 
            AddGroup();
 
            return Unit();
        }
 
        /*
         * Simple parsing for replacement patterns
         */
        internal RegexNode ScanReplacement() {
            int c;
            int startpos;
 
            _concatenation = new RegexNode(RegexNode.Concatenate, _options);
 
            for (;;) {
                c = CharsRight();
                if (c == 0)
                    break;
 
                startpos = Textpos();
 
                while (c > 0 && RightChar() != '$') {
                    MoveRight();
                    c--;
                }
 
                AddConcatenate(startpos, Textpos() - startpos, true);
 
                if (c > 0) {
                    if (MoveRightGetChar() == '$')
                        AddUnitNode(ScanDollar());
                    AddConcatenate();
                }
            }
 
            return _concatenation;
        }
 
        /*
         * Scans contents of [] (not including []'s), and converts to a
         * RegexCharClass.
         */
        internal RegexCharClass ScanCharClass(bool caseInsensitive) {
            return ScanCharClass(caseInsensitive, false);
        }
 
        /*
         * Scans contents of [] (not including []'s), and converts to a
         * RegexCharClass.
         */
        internal RegexCharClass ScanCharClass(bool caseInsensitive, bool scanOnly) {
            char    ch = '\0';
            char    chPrev = '\0';
            bool inRange = false;
            bool firstChar = true;
            bool closed = false;
 
            RegexCharClass cc;
 
            cc = scanOnly ? null : new RegexCharClass();
 
            if (CharsRight() > 0 && RightChar() == '^') {
                MoveRight();
                if (!scanOnly)
                    cc.Negate = true;
            }
 
            for ( ; CharsRight() > 0; firstChar = false) {
                bool fTranslatedChar = false;
                ch = MoveRightGetChar();
                if (ch == ']') {
                    if (!firstChar) {
                        closed = true;
                        break;
                    }
                }
                else if (ch == '\\' && CharsRight() > 0) {
 
                    switch (ch = MoveRightGetChar()) {
                        case 'D':
                        case 'd':
                            if (!scanOnly) {
                                if (inRange)
                                    throw MakeException(SR.GetString(SR.BadClassInCharRange, ch.ToString()));
                                cc.AddDigit(UseOptionE(), ch == 'D', _pattern);
                            }
                            continue;
 
                        case 'S':
                        case 's':
                            if (!scanOnly) {
                                if (inRange)
                                    throw MakeException(SR.GetString(SR.BadClassInCharRange, ch.ToString()));
                                cc.AddSpace(UseOptionE(), ch == 'S');
                            }
                            continue;
 
                        case 'W':
                        case 'w':
                            if (!scanOnly) {
                                if (inRange)
                                    throw MakeException(SR.GetString(SR.BadClassInCharRange, ch.ToString()));
 
                                cc.AddWord(UseOptionE(), ch == 'W');
                            }
                            continue;
 
                        case 'p':
                        case 'P':
                            if (!scanOnly) {
                                if (inRange)
                                    throw MakeException(SR.GetString(SR.BadClassInCharRange, ch.ToString()));
                                cc.AddCategoryFromName(ParseProperty(), (ch != 'p'), caseInsensitive, _pattern);
                            }
                            else 
                                ParseProperty();
 
                            continue;
                            
                        case '-':
                            if (!scanOnly)
                                cc.AddRange(ch, ch);
                            continue;
                            
                        default:
                            MoveLeft();
                            ch = ScanCharEscape(); // non-literal character
                            fTranslatedChar = true;
                            break;          // this break will only break out of the switch
                    }
                }
                else if (ch == '[') {
                    // This is code for Posix style properties - [:Ll:] or [:IsTibetan:].
                    // It currently doesn't do anything other than skip the whole thing!
                    if (CharsRight() > 0 && RightChar() == ':' && !inRange) {
                        String name;
                        int savePos = Textpos();
 
                        MoveRight();
                        name = ScanCapname();
                        if (CharsRight() < 2 || MoveRightGetChar() != ':' || MoveRightGetChar() != ']')
                            Textto(savePos);
                        // else lookup name (nyi)
                    }
                }
 
                
                if (inRange) {
                    inRange = false;
                    if (!scanOnly) {
                        if (ch == '[' && !fTranslatedChar && !firstChar) {
                            // We thought we were in a range, but we're actually starting a subtraction. 
                            // In that case, we'll add chPrev to our char class, skip the opening [, and
                            // scan the new character class recursively. 
                            cc.AddChar(chPrev);
                            cc.AddSubtraction(ScanCharClass(caseInsensitive, false));
 
                            if (CharsRight() > 0 && RightChar() != ']')
                                throw MakeException(SR.GetString(SR.SubtractionMustBeLast));
                        } 
                        else {
                            // a regular range, like a-z
                            if (chPrev > ch)
                                throw MakeException(SR.GetString(SR.ReversedCharRange));
                            cc.AddRange(chPrev, ch);
                        }
                    }
                }
                else if (CharsRight() >= 2 && RightChar() == '-' && RightChar(1) != ']') {
                    // this could be the start of a range
                    chPrev = ch;
                    inRange = true;
                    MoveRight();
                }
                else if (CharsRight() >= 1 && ch == '-' && !fTranslatedChar && RightChar() == '[' && !firstChar) {
                    // we aren't in a range, and now there is a subtraction.  Usually this happens
                    // only when a subtraction follows a range, like [a-z-[b]]
                    if (!scanOnly) {
                        MoveRight(1);
                        cc.AddSubtraction(ScanCharClass(caseInsensitive, false));
 
                        if (CharsRight() > 0 && RightChar() != ']')
                            throw MakeException(SR.GetString(SR.SubtractionMustBeLast));
                    }
                    else {
                        MoveRight(1);
                        ScanCharClass(caseInsensitive, true);
                    }
                }
                else {
                    if (!scanOnly)
                        cc.AddRange(ch, ch);
                }
            }
 
            if (!closed)
                throw MakeException(SR.GetString(SR.UnterminatedBracket));
 
            if (!scanOnly && caseInsensitive)
                cc.AddLowercase(_culture);
            
            return cc;
        }
 
        /*
         * Scans chars following a '(' (not counting the '('), and returns
         * a RegexNode for the type of group scanned, or null if the group
         * simply changed options (?cimsx-cimsx) or was a comment (#...).
         */
        internal RegexNode ScanGroupOpen() {
            char ch = '\0';
            int NodeType;
            char close = '>';
 
 
            // just return a RegexNode if we have:
            // 1. "(" followed by nothing
            // 2. "(x" where x != ?
            // 3. "(?)"
            if (CharsRight() == 0 || RightChar() != '?' || (RightChar() == '?' && (CharsRight() > 1 && RightChar(1) == ')'))) {
                if (UseOptionN() || _ignoreNextParen) {
                    _ignoreNextParen = false;
                    return new RegexNode(RegexNode.Group, _options);
                }
                else
                    return new RegexNode(RegexNode.Capture, _options, _autocap++, -1);
            }
 
            MoveRight();
 
            for (;;) {
                if (CharsRight() == 0)
                    break;
 
                switch (ch = MoveRightGetChar()) {
                    case ':':
                        NodeType = RegexNode.Group;
                        break;
 
                    case '=':
                        _options &= ~(RegexOptions.RightToLeft);
                        NodeType = RegexNode.Require;
                        break;
 
                    case '!':
                        _options &= ~(RegexOptions.RightToLeft);
                        NodeType = RegexNode.Prevent;
                        break;
 
                    case '>':
                        NodeType = RegexNode.Greedy;
                        break;
 
                    case '\'':
                        close = '\'';
                        goto case '<';
                        // fallthrough
 
                    case '<':
                        if (CharsRight() == 0)
                            goto BreakRecognize;
 
                        switch (ch = MoveRightGetChar()) {
                            case '=':
                                if (close == '\'')
                                    goto BreakRecognize;
 
                                _options |= RegexOptions.RightToLeft;
                                NodeType = RegexNode.Require;
                                break;
 
                            case '!':
                                if (close == '\'')
                                    goto BreakRecognize;
 
                                _options |= RegexOptions.RightToLeft;
                                NodeType = RegexNode.Prevent;
                                break;
 
                            default:
                                MoveLeft();
                                int capnum = -1;
                                int uncapnum = -1;
                                bool proceed = false;
 
                                // grab part before -
 
                                if (ch >= '0' && ch <= '9') {
                                    capnum = ScanDecimal();
 
                                    if (!IsCaptureSlot(capnum))
                                        capnum = -1;
 
                                    // check if we have bogus characters after the number
                                    if (CharsRight() > 0 && !(RightChar() == close || RightChar() == '-'))
                                        throw MakeException(SR.GetString(SR.InvalidGroupName));
                                    if (capnum == 0)
                                        throw MakeException(SR.GetString(SR.CapnumNotZero));
                                }
                                else if (RegexCharClass.IsWordChar(ch)) {
                                    String capname = ScanCapname();
 
                                    if (IsCaptureName(capname))
                                        capnum = CaptureSlotFromName(capname);
 
                                    // check if we have bogus character after the name
                                    if (CharsRight() > 0 && !(RightChar() == close || RightChar() == '-'))
                                        throw MakeException(SR.GetString(SR.InvalidGroupName));
                                }
                                else if (ch == '-') {
                                    proceed = true;
                                }
                                else {
                                    // bad group name - starts with something other than a word character and isn't a number
                                    throw MakeException(SR.GetString(SR.InvalidGroupName));
                                }
 
                                // grab part after - if any
 
                                if ((capnum != -1 || proceed == true) && CharsRight() > 0 && RightChar() == '-') {
                                    MoveRight();
                                    ch = RightChar();
 
                                    if (ch >= '0' && ch <= '9') {
                                        uncapnum = ScanDecimal();
                                        
                                        if (!IsCaptureSlot(uncapnum))
                                            throw MakeException(SR.GetString(SR.UndefinedBackref, uncapnum));
                                        
                                        // check if we have bogus characters after the number
                                        if (CharsRight() > 0 && RightChar() != close)
                                            throw MakeException(SR.GetString(SR.InvalidGroupName));
                                    }
                                    else if (RegexCharClass.IsWordChar(ch)) {
                                        String uncapname = ScanCapname();
 
                                        if (IsCaptureName(uncapname))
                                            uncapnum = CaptureSlotFromName(uncapname);
                                        else
                                            throw MakeException(SR.GetString(SR.UndefinedNameRef, uncapname));
 
                                        // check if we have bogus character after the name
                                        if (CharsRight() > 0 && RightChar() != close)
                                            throw MakeException(SR.GetString(SR.InvalidGroupName));
                                    }
                                    else {
                                        // bad group name - starts with something other than a word character and isn't a number
                                        throw MakeException(SR.GetString(SR.InvalidGroupName));
                                    }
                                }
 
                                // actually make the node
 
                                if ((capnum != -1 || uncapnum != -1) && CharsRight() > 0 && MoveRightGetChar() == close) {
                                    return new RegexNode(RegexNode.Capture, _options, capnum, uncapnum);
                                }
                                goto BreakRecognize;
                        }
                        break;
 
                    case '(': 
                        // alternation construct (?(...) | )
                 
                        int parenPos = Textpos();
                        if (CharsRight() > 0)   	
                        {
                            ch = RightChar();
    
                            // check if the alternation condition is a backref
                            if (ch >= '0' && ch <= '9') {
                                int capnum = ScanDecimal();
                                if (CharsRight() > 0 && MoveRightGetChar() == ')') {
                                    if (IsCaptureSlot(capnum))
                                        return new RegexNode(RegexNode.Testref, _options, capnum);
                                    else
                                        throw MakeException(SR.GetString(SR.UndefinedReference, capnum.ToString(CultureInfo.CurrentCulture)));
                                }
                                else
                                    throw MakeException(SR.GetString(SR.MalformedReference, capnum.ToString(CultureInfo.CurrentCulture)));
    
                            }
                            else if (RegexCharClass.IsWordChar(ch)) {
                                String capname = ScanCapname();
    
                                if (IsCaptureName(capname) && CharsRight() > 0 && MoveRightGetChar() == ')')
                                    return new RegexNode(RegexNode.Testref, _options, CaptureSlotFromName(capname));
                            }
                        }
                        // not a backref
                        NodeType = RegexNode.Testgroup;
                        Textto(parenPos - 1);       // jump to the start of the parentheses
                        _ignoreNextParen = true;    // but make sure we don't try to capture the insides
 
                        int charsRight = CharsRight();
                        if (charsRight >= 3 && RightChar(1) == '?') {
                            char rightchar2 = RightChar(2);
                            // disallow comments in the condition
                            if (rightchar2 == '#')
                                throw MakeException(SR.GetString(SR.AlternationCantHaveComment));
 
                            // disallow named capture group (?<..>..) in the condition
                            if (rightchar2 == '\'' ) 
                                throw MakeException(SR.GetString(SR.AlternationCantCapture));
                            else {
                                if (charsRight >=4 && (rightchar2 == '<' && RightChar(3) != '!' && RightChar(3) != '='))
                                    throw MakeException(SR.GetString(SR.AlternationCantCapture));
                            }
                        }
                            
                        break;
 
 
                    default:
                        MoveLeft();
 
                        NodeType = RegexNode.Group;
                        ScanOptions();
                        if (CharsRight() == 0)
                            goto BreakRecognize;
 
                        if ((ch = MoveRightGetChar()) == ')')
                            return null;
 
                        if (ch != ':')
                            goto BreakRecognize;
                        break;
                }
 
                return new RegexNode(NodeType, _options);
            }
 
            BreakRecognize: 
            ;
            // break Recognize comes here
 
            throw MakeException(SR.GetString(SR.UnrecognizedGrouping));
        }
 
        /*
         * Scans whitespace or x-mode comments.
         */
        internal void ScanBlank() {
            if (UseOptionX()) {
                for (;;) {
                    while (CharsRight() > 0 && IsSpace(RightChar()))
                        MoveRight();
 
                    if (CharsRight() == 0)
                        break;
 
                    if (RightChar() == '#') {
                        while (CharsRight() > 0 && RightChar() != '\n')
                            MoveRight();
                    }
                    else if (CharsRight() >= 3 && RightChar(2) == '#' &&
                             RightChar(1) == '?' && RightChar() == '(') {
                        while (CharsRight() > 0 && RightChar() != ')')
                            MoveRight();
                        if (CharsRight() == 0)
                            throw MakeException(SR.GetString(SR.UnterminatedComment));
                        MoveRight();
                    }
                    else
                        break;
                }
            }
            else {
                for (;;) {
                    if (CharsRight() < 3 || RightChar(2) != '#' ||
                        RightChar(1) != '?' || RightChar() != '(')
                        return;
 
                    while (CharsRight() > 0 && RightChar() != ')')
                        MoveRight();
                    if (CharsRight() == 0)
                        throw MakeException(SR.GetString(SR.UnterminatedComment));
                    MoveRight();
                }
            }
        }
 
        /*
         * Scans chars following a '\' (not counting the '\'), and returns
         * a RegexNode for the type of atom scanned.
         */
        internal RegexNode ScanBackslash() {
            char ch;
            RegexCharClass cc;
 
            if (CharsRight() == 0)
                throw MakeException(SR.GetString(SR.IllegalEndEscape));
 
            switch (ch = RightChar()) {
                case 'b':
                case 'B':
                case 'A':
                case 'G':
                case 'Z':
                case 'z':
                    MoveRight();
                    return new RegexNode(TypeFromCode(ch), _options);
 
                case 'w':
                    MoveRight();
                    if (UseOptionE())
                        return new RegexNode(RegexNode.Set, _options, RegexCharClass.ECMAWordClass);
                    return new RegexNode(RegexNode.Set, _options, RegexCharClass.WordClass);
 
                case 'W':
                    MoveRight();
                    if (UseOptionE())
                        return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotECMAWordClass);
                    return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotWordClass);
 
                case 's':
                    MoveRight();
                    if (UseOptionE())
                        return new RegexNode(RegexNode.Set, _options, RegexCharClass.ECMASpaceClass);
                    return new RegexNode(RegexNode.Set, _options, RegexCharClass.SpaceClass);
 
                case 'S':
                    MoveRight();
                    if (UseOptionE())
                        return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotECMASpaceClass);
                    return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotSpaceClass);
 
                case 'd':
                    MoveRight();
                    if (UseOptionE())
                        return new RegexNode(RegexNode.Set, _options, RegexCharClass.ECMADigitClass);
                    return new RegexNode(RegexNode.Set, _options, RegexCharClass.DigitClass);
 
                case 'D':
                    MoveRight();
                    if (UseOptionE())
                        return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotECMADigitClass);
                    return new RegexNode(RegexNode.Set, _options, RegexCharClass.NotDigitClass);
 
                case 'p':
                case 'P':
                    MoveRight();
                    cc = new RegexCharClass();
                    cc.AddCategoryFromName(ParseProperty(), (ch != 'p'), UseOptionI(), _pattern);
                    if (UseOptionI())
                        cc.AddLowercase(_culture);
                    
                    return new RegexNode(RegexNode.Set, _options, cc.ToStringClass());
 
                default:
                    return ScanBasicBackslash();
            }
        }
 
        /*
         * Scans \-style backreferences and character escapes
         */
        internal RegexNode ScanBasicBackslash() {
            if (CharsRight() == 0)
                throw MakeException(SR.GetString(SR.IllegalEndEscape));
 
            char ch;
            bool angled = false;
            char close = '\0';
            int backpos;
 
            backpos = Textpos();
            ch = RightChar();
 
            // allow \k<foo> instead of \<foo>, which is now deprecated
 
            if (ch == 'k') {
                if (CharsRight() >= 2) {
                    MoveRight();
                    ch = MoveRightGetChar();
 
                    if (ch == '<' || ch == '\'') {
                        angled = true;
                        close = (ch == '\'') ? '\'' : '>';
                    }
                }
 
                if (!angled || CharsRight() <= 0)
                    throw MakeException(SR.GetString(SR.MalformedNameRef));
 
                ch = RightChar();
            }
 
            // Note angle without \g <
 
            else if ((ch == '<' || ch == '\'') && CharsRight() > 1) {
                angled = true;
                close = (ch == '\'') ? '\'' : '>';
 
                MoveRight();
                ch = RightChar();
            }
 
            // Try to parse backreference: \<1> or \<cap>
 
            if (angled && ch >= '0' && ch <= '9') {
                int capnum = ScanDecimal();
 
                if (CharsRight() > 0 && MoveRightGetChar() == close) {
                    if (IsCaptureSlot(capnum))
                        return new RegexNode(RegexNode.Ref, _options, capnum);
                    else
                        throw MakeException(SR.GetString(SR.UndefinedBackref, capnum.ToString(CultureInfo.CurrentCulture)));
                }
            }
 
            // Try to parse backreference or octal: \1
 
            else if (!angled && ch >= '1' && ch <= '9') {
                if (UseOptionE()) {
                    int capnum = -1;
                    int newcapnum = (int)(ch - '0');
                    int pos = Textpos() - 1;
                    while (newcapnum <= _captop) {
                        if (IsCaptureSlot(newcapnum) && (_caps == null || (int)_caps[newcapnum] < pos))
                            capnum = newcapnum;
                        MoveRight();
                        if (CharsRight() == 0 || (ch = RightChar()) < '0' || ch > '9')
                            break;
                        newcapnum = newcapnum * 10 + (int)(ch - '0');
                    }
                    if (capnum >= 0)
                        return new RegexNode(RegexNode.Ref, _options, capnum);
                } else
                {
 
                  int capnum = ScanDecimal();
                  if (IsCaptureSlot(capnum))
                      return new RegexNode(RegexNode.Ref, _options, capnum);
                  else if (capnum <= 9)
                      throw MakeException(SR.GetString(SR.UndefinedBackref, capnum.ToString(CultureInfo.CurrentCulture)));
                }
            }
 
            else if (angled && RegexCharClass.IsWordChar(ch)) {
                String capname = ScanCapname();
 
                if (CharsRight() > 0 && MoveRightGetChar() == close) {
                    if (IsCaptureName(capname))
                        return new RegexNode(RegexNode.Ref, _options, CaptureSlotFromName(capname));
                    else
                        throw MakeException(SR.GetString(SR.UndefinedNameRef, capname));
                }
            }
 
            // Not backreference: must be char code
 
            Textto(backpos);
            ch = ScanCharEscape();
 
            if (UseOptionI())
                ch = Char.ToLower(ch, _culture);
 
            return new RegexNode(RegexNode.One, _options, ch);
        }
 
        /*
         * Scans $ patterns recognized within replacment patterns
         */
        internal RegexNode ScanDollar() {
            if (CharsRight() == 0)
                return new RegexNode(RegexNode.One, _options, '$');
 
            char ch = RightChar();
            bool angled;
            int backpos = Textpos();
            int lastEndPos = backpos;
 
            // Note angle
 
            if (ch == '{' && CharsRight() > 1) {
                angled = true;
                MoveRight();
                ch = RightChar();
            }
            else {
                angled = false;
            }
 
            // Try to parse backreference: \1 or \{1} or \{cap}
 
            if (ch >= '0' && ch <= '9') {
                if (!angled && UseOptionE()) {
                    int capnum = -1;
                    int newcapnum = (int)(ch - '0');
                    MoveRight();
                    if (IsCaptureSlot(newcapnum)) {
                        capnum = newcapnum;
                        lastEndPos = Textpos();
                    }
 
                    while (CharsRight() > 0 && (ch = RightChar()) >= '0' && ch <= '9') {
                        int digit = (int)(ch - '0');
                        if (newcapnum > (MaxValueDiv10) || (newcapnum == (MaxValueDiv10) && digit > (MaxValueMod10)))
                            throw MakeException(SR.GetString(SR.CaptureGroupOutOfRange));
 
                        newcapnum = newcapnum * 10 + digit;
 
                        MoveRight();
                        if (IsCaptureSlot(newcapnum)) {
                            capnum = newcapnum;
                            lastEndPos = Textpos();
                        }
                    }
                    Textto(lastEndPos);
                    if (capnum >= 0)
                        return new RegexNode(RegexNode.Ref, _options, capnum);
                } 
                else
                {
                    int capnum = ScanDecimal();
                    if (!angled || CharsRight() > 0 && MoveRightGetChar() == '}') {
                        if (IsCaptureSlot(capnum))
                            return new RegexNode(RegexNode.Ref, _options, capnum);
                    }
                }
            }
            else if (angled && RegexCharClass.IsWordChar(ch)) {
                String capname = ScanCapname();
 
                if (CharsRight() > 0 && MoveRightGetChar() == '}') {
                    if (IsCaptureName(capname))
                        return new RegexNode(RegexNode.Ref, _options, CaptureSlotFromName(capname));
                }
            }
            else if (!angled) {
                int capnum = 1;
 
                switch (ch) {
                    case '$':
                        MoveRight();
                        return new RegexNode(RegexNode.One, _options, '$');
 
                    case '&':
                        capnum = 0;
                        break;
 
                    case '`':
                        capnum = RegexReplacement.LeftPortion;
                        break;
 
                    case '\'':
                        capnum = RegexReplacement.RightPortion;
                        break;
 
                    case '+':
                        capnum = RegexReplacement.LastGroup;
                        break;
 
                    case '_':
                        capnum = RegexReplacement.WholeString;
                        break;
                }
 
                if (capnum != 1) {
                    MoveRight();
                    return new RegexNode(RegexNode.Ref, _options, capnum);
                }
            }
 
            // unrecognized $: literalize
 
            Textto(backpos);
            return new RegexNode(RegexNode.One, _options, '$');
        }
 
        /*
         * Scans a capture name: consumes word chars
         */
        internal String ScanCapname() {
            int startpos = Textpos();
 
            while (CharsRight() > 0) {
                if (!RegexCharClass.IsWordChar(MoveRightGetChar())) {
                    MoveLeft();
                    break;
                }
            }
 
            return _pattern.Substring(startpos, Textpos() - startpos);
        }
 
 
        /*
         * Scans up to three octal digits (stops before exceeding 0377).
         */
        internal char ScanOctal() {
            int d;
            int i;
            int c;
 
            // Consume octal chars only up to 3 digits and value 0377
 
            c = 3;
 
            if (c > CharsRight())
                c = CharsRight();
 
            for (i = 0; c > 0 && (uint)(d = RightChar() - '0') <= 7; c -= 1) {
                MoveRight();
                i *= 8;
                i += d;
                if (UseOptionE() && i >= 0x20)
                    break;
            }
 
            // Octal codes only go up to 255.  Any larger and the behavior that Perl follows
            // is simply to truncate the high bits. 
            i &= 0xFF;
            
            return(char)i;
        }
 
        /*
         * Scans any number of decimal digits (pegs value at 2^31-1 if too large)
         */
        internal int ScanDecimal() {
            int i = 0;
            int d;
 
            while (CharsRight() > 0 && (uint)(d = (char)(RightChar() - '0')) <= 9) {
                MoveRight();
 
                if (i > (MaxValueDiv10) || (i == (MaxValueDiv10) && d > (MaxValueMod10)))
                    throw MakeException(SR.GetString(SR.CaptureGroupOutOfRange));
 
                i *= 10;
                i += d;
            }
 
            return i;
        }
 
        /*
         * Scans exactly c hex digits (c=2 for \xFF, c=4 for \uFFFF)
         */
        internal char ScanHex(int c) {
            int i;
            int d;
 
            i = 0;
 
            if (CharsRight() >= c) {
                for (; c > 0 && ((d = HexDigit(MoveRightGetChar())) >= 0); c -= 1) {
                    i *= 0x10;
                    i += d;
                }
            }
 
            if (c > 0)
                throw MakeException(SR.GetString(SR.TooFewHex));
 
            return(char)i;
        }
 
        /*
         * Returns n <= 0xF for a hex digit.
         */
        internal static int HexDigit(char ch) {
            int d;
 
            if ((uint)(d = ch - '0') <= 9)
                return d;
 
            if ((uint)(d = ch - 'a') <= 5)
                return d + 0xa;
 
            if ((uint)(d = ch - 'A') <= 5)
                return d + 0xa;
 
            return -1;
        }
 
        /*
         * Grabs and converts an ascii control character
         */
        internal char ScanControl() {
            char ch;
 
            if (CharsRight() <= 0)
                throw MakeException(SR.GetString(SR.MissingControl));
 
            ch = MoveRightGetChar();
 
            // \ca interpreted as \cA
 
            if (ch >= 'a' && ch <= 'z')
                ch = (char)(ch - ('a' - 'A'));
 
            if ((ch = (char)(ch - '@')) < ' ')
                return ch;
 
            throw MakeException(SR.GetString(SR.UnrecognizedControl));
        }
 
        /*
         * Returns true for options allowed only at the top level
         */
        internal bool IsOnlyTopOption(RegexOptions option) {
            return(option == RegexOptions.RightToLeft
#if !SILVERLIGHT
                || option == RegexOptions.Compiled
#endif
                || option == RegexOptions.CultureInvariant
                || option == RegexOptions.ECMAScript
            );
        }
 
        /*
         * Scans cimsx-cimsx option string, stops at the first unrecognized char.
         */
        internal void ScanOptions() {
            char ch;
            bool off;
            RegexOptions option;
 
            for (off = false; CharsRight() > 0; MoveRight()) {
                ch = RightChar();
 
                if (ch == '-') {
                    off = true;
                }
                else if (ch == '+') {
                    off = false;
                }
                else {
                    option = OptionFromCode(ch);
                    if (option == 0 || IsOnlyTopOption(option))
                        return;
 
                    if (off)
                        _options &= ~option;
                    else
                        _options |= option;
                }
            }
        }
 
        /*
         * Scans \ code for escape codes that map to single unicode chars.
         */
        internal char ScanCharEscape() {
            char ch;
 
            ch = MoveRightGetChar();
 
            if (ch >= '0' && ch <= '7') {
                MoveLeft();
                return ScanOctal();
            }
 
            switch (ch) {
                case 'x':
                    return ScanHex(2);
                case 'u':
                    return ScanHex(4);
                case 'a':
                    return '\u0007';
                case 'b':
                    return '\b';
                case 'e':
                    return '\u001B';
                case 'f':
                    return '\f';
                case 'n':
                    return '\n';
                case 'r':
                    return '\r';
                case 't':
                    return '\t';
                case 'v':
                    return '\u000B';
                case 'c':
                    return ScanControl();
                default:
                    if (!UseOptionE() && RegexCharClass.IsWordChar(ch))
                        throw MakeException(SR.GetString(SR.UnrecognizedEscape, ch.ToString()));
                    return ch;
            }
        }
 
        /*
         * Scans X for \p{X} or \P{X}
         */
        internal String ParseProperty() {
            if (CharsRight() < 3) {
                throw MakeException(SR.GetString(SR.IncompleteSlashP));
            }
            char ch = MoveRightGetChar();
            if (ch != '{') {
                throw MakeException(SR.GetString(SR.MalformedSlashP));
            }
            
            int startpos = Textpos();
            while (CharsRight() > 0) {
                ch = MoveRightGetChar();
                if (!(RegexCharClass.IsWordChar(ch) || ch == '-')) {
                    MoveLeft();
                    break;
                }
            }
            String capname = _pattern.Substring(startpos, Textpos() - startpos);
 
            if (CharsRight() == 0 || MoveRightGetChar() != '}')
                throw MakeException(SR.GetString(SR.IncompleteSlashP));
 
            return capname;
        }
 
        /*
         * Returns ReNode type for zero-length assertions with a \ code.
         */
        internal int TypeFromCode(char ch) {
            switch (ch) {
                case 'b':
                    return UseOptionE() ? RegexNode.ECMABoundary : RegexNode.Boundary;
                case 'B':
                    return UseOptionE() ? RegexNode.NonECMABoundary : RegexNode.Nonboundary;
                case 'A':
                    return RegexNode.Beginning;
                case 'G':
                    return RegexNode.Start;
                case 'Z':
                    return RegexNode.EndZ;
                case 'z':
                    return RegexNode.End;
                default:
                    return RegexNode.Nothing;
            }
        }
 
        /*
         * Returns option bit from single-char (?cimsx) code.
         */
        internal static RegexOptions OptionFromCode(char ch) {
            // case-insensitive
            if (ch >= 'A' && ch <= 'Z')
                ch += (char)('a' - 'A');
 
            switch (ch) {
#if !SILVERLIGHT
                case 'c':
                    return RegexOptions.Compiled;
#endif
                case 'i':
                    return RegexOptions.IgnoreCase;
                case 'r':
                    return RegexOptions.RightToLeft;
                case 'm':
                    return RegexOptions.Multiline;
                case 'n':
                    return RegexOptions.ExplicitCapture;
                case 's':
                    return RegexOptions.Singleline;
                case 'x':
                    return RegexOptions.IgnorePatternWhitespace;
#if DBG
                case 'd':
                    return RegexOptions.Debug;
#endif
                case 'e':
                    return RegexOptions.ECMAScript;
                default:
                    return 0;
            }
        }
 
        /*
         * a prescanner for deducing the slots used for
         * captures by doing a partial tokenization of the pattern.
         */
        internal void CountCaptures() {
            char ch;
 
            NoteCaptureSlot(0, 0);
 
            _autocap = 1;
 
            while (CharsRight() > 0) {
                int pos = Textpos();
                ch = MoveRightGetChar();
                switch (ch) {
                    case '\\':
                        if (CharsRight() > 0)
                            MoveRight();
                        break;
 
                    case '#':
                        if (UseOptionX()) {
                            MoveLeft();
                            ScanBlank();
                        }
                        break;
 
                    case '[':
                        ScanCharClass(false, true);
                        break;
 
                    case ')':
                        if (!EmptyOptionsStack())
                            PopOptions();
                        break;
 
                    case '(':
                        if (CharsRight() >= 2 && RightChar(1) == '#' && RightChar() == '?') {
                            MoveLeft();
                            ScanBlank();
                        } 
                        else {
                            
                            PushOptions();
                            if (CharsRight() > 0 && RightChar() == '?') {
                                // we have (?...
                                MoveRight();
 
                                if (CharsRight() > 1 && (RightChar() == '<' || RightChar() == '\'')) {
                                    // named group: (?<... or (?'...
 
                                    MoveRight();
                                    ch = RightChar();
 
                                    if (ch != '0' && RegexCharClass.IsWordChar(ch)) {
                                        //if (_ignoreNextParen) 
                                        //    throw MakeException(SR.GetString(SR.AlternationCantCapture));
                                        if (ch >= '1' && ch <= '9') 
                                            NoteCaptureSlot(ScanDecimal(), pos);
                                        else 
                                            NoteCaptureName(ScanCapname(), pos);
                                    }
                                }
                                else {
                                    // (?...
 
                                    // get the options if it's an option construct (?cimsx-cimsx...)
                                    ScanOptions();
 
                                    if (CharsRight() > 0) {
                                        if (RightChar() == ')') {
                                            // (?cimsx-cimsx)
                                            MoveRight();
                                            PopKeepOptions();
                                        }
                                        else if (RightChar() == '(') {
                                            // alternation construct: (?(foo)yes|no)
                                            // ignore the next paren so we don't capture the condition
                                            _ignoreNextParen = true;
 
                                            // break from here so we don't reset _ignoreNextParen
                                            break;
                                        }
                                    }
                                }
                            }
                            else {
                                if (!UseOptionN() && !_ignoreNextParen)
                                    NoteCaptureSlot(_autocap++, pos);
                            }
                        }
 
                        _ignoreNextParen = false;
                        break;
                }
            }
 
            AssignNameSlots();
        }
 
        /*
         * Notes a used capture slot
         */
        internal void NoteCaptureSlot(int i, int pos) {
            if (!_caps.ContainsKey(i)) {
                // the rhs of the hashtable isn't used in the parser
 
                _caps.Add(i, pos);
                _capcount++;
 
                if (_captop <= i) {
                    if (i == Int32.MaxValue)
                        _captop = i;
                    else
                        _captop = i + 1;
                }
            }
        }
 
        /*
         * Notes a used capture slot
         */
        internal void NoteCaptureName(String name, int pos) {
            if (_capnames == null) {
#if SILVERLIGHT
                _capnames = new Dictionary<String, Int32>();
#else
                _capnames = new Hashtable();
#endif
                _capnamelist = new List<String>();
            }
 
            if (!_capnames.ContainsKey(name)) {
                _capnames.Add(name, pos);
                _capnamelist.Add(name);
            }
        }
 
        /*
         * For when all the used captures are known: note them all at once
         */
#if SILVERLIGHT
        internal void NoteCaptures(Dictionary<Int32, Int32> caps, int capsize, Dictionary<String, Int32> capnames) {
#else
        internal void NoteCaptures(Hashtable caps, int capsize, Hashtable capnames) {
#endif
            _caps = caps;
            _capsize = capsize;
            _capnames = capnames;
        }
 
        /*
         * Assigns unused slot numbers to the capture names
         */
        internal void AssignNameSlots() {
            if (_capnames != null) {
                for (int i = 0; i < _capnamelist.Count; i++) {
                    while (IsCaptureSlot(_autocap))
                        _autocap++;
                    string name = _capnamelist[i];
                    int pos = (int)_capnames[name];
                    _capnames[name] = _autocap;
                    NoteCaptureSlot(_autocap, pos);
 
                    _autocap++;
                }
            }
 
            // if the caps array has at least one gap, construct the list of used slots
 
            if (_capcount < _captop) {
                _capnumlist = new Int32[_capcount];
                int i = 0;
 
                for (IDictionaryEnumerator de = _caps.GetEnumerator(); de.MoveNext(); )
                    _capnumlist[i++] = (int)de.Key;
 
                System.Array.Sort(_capnumlist, Comparer<Int32>.Default);
            }
 
            // merge capsnumlist into capnamelist
 
            if (_capnames != null || _capnumlist != null) {
                List<String> oldcapnamelist;
                int next;
                int k = 0;
 
                if (_capnames == null) {
                    oldcapnamelist = null;
#if SILVERLIGHT
                    _capnames = new Dictionary<String, Int32>();
#else
                    _capnames = new Hashtable();
#endif
                    _capnamelist = new List<String>();
                    next = -1;
                }
                else {
                    oldcapnamelist = _capnamelist;
                    _capnamelist = new List<String>();
                    next = (int)_capnames[oldcapnamelist[0]];
                }
 
                for (int i = 0; i < _capcount; i++) {
                    int j = (_capnumlist == null) ? i : (int)_capnumlist[i];
 
                    if (next == j) {
                        _capnamelist.Add(oldcapnamelist[k++]);
                        next = (k == oldcapnamelist.Count) ? -1 : (int)_capnames[oldcapnamelist[k]];
                    }
                    else {
                        String str = Convert.ToString(j, _culture);
                        _capnamelist.Add(str);
                        _capnames[str] = j;
                    }
                }
            }
        }
 
        /*
         * Looks up the slot number for a given name
         */
        internal int CaptureSlotFromName(String capname) {
            return(int)_capnames[capname];
        }
 
        /*
         * True if the capture slot was noted
         */
        internal bool IsCaptureSlot(int i) {
            if (_caps != null)
                return _caps.ContainsKey(i);
 
            return(i >= 0 && i < _capsize);
        }
 
        /*
         * Looks up the slot number for a given name
         */
        internal bool IsCaptureName(String capname) {
            if (_capnames == null)
                return false;
 
            return _capnames.ContainsKey(capname);
        }
 
        /*
         * True if N option disabling '(' autocapture is on.
         */
        internal bool UseOptionN() {
            return(_options & RegexOptions.ExplicitCapture) != 0;
        }
 
        /*
         * True if I option enabling case-insensitivity is on.
         */
        internal bool UseOptionI() {
            return(_options & RegexOptions.IgnoreCase) != 0;
        }
 
        /*
         * True if M option altering meaning of $ and ^ is on.
         */
        internal bool UseOptionM() {
            return(_options & RegexOptions.Multiline) != 0;
        }
 
        /*
         * True if S option altering meaning of . is on.
         */
        internal bool UseOptionS() {
            return(_options & RegexOptions.Singleline) != 0;
        }
 
        /*
         * True if X option enabling whitespace/comment mode is on.
         */
        internal bool UseOptionX() {
            return(_options & RegexOptions.IgnorePatternWhitespace) != 0;
        }
 
        /*
         * True if E option enabling ECMAScript behavior is on.
         */
        internal bool UseOptionE() {
            return(_options & RegexOptions.ECMAScript) != 0;
        }
 
        internal const byte Q = 5;    // quantifier
        internal const byte S = 4;    // ordinary stoppper
        internal const byte Z = 3;    // ScanBlank stopper
        internal const byte X = 2;    // whitespace
        internal const byte E = 1;    // should be escaped
 
        /*
         * For categorizing ascii characters.
        */
        internal static readonly byte[] _category = new byte[] {
            // 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 
               0,0,0,0,0,0,0,0,0,X,X,0,X,X,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            //   ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 
               X,0,0,Z,S,0,0,0,S,S,Q,Q,0,0,S,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,Q,
            // @ 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 [ \ ] ^ _
               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,S,S,0,S,0,
            // ' 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 { | } ~ 
               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,Q,S,0,0,0};
 
        /*
         * Returns true for those characters that terminate a string of ordinary chars.
         */
        internal static bool IsSpecial(char ch) {
            return(ch <= '|' && _category[ch] >= S);
        }
 
        /*
         * Returns true for those characters that terminate a string of ordinary chars.
         */
        internal static bool IsStopperX(char ch) {
            return(ch <= '|' && _category[ch] >= X);
        }
 
        /*
         * Returns true for those characters that begin a quantifier.
         */
        internal static bool IsQuantifier(char ch) {
            return(ch <= '{' && _category[ch] >= Q);
        }
 
        internal bool IsTrueQuantifier() {
            int nChars = CharsRight();
            if (nChars == 0)
                return false;
            int startpos = Textpos();
            char ch = CharAt(startpos);
            if (ch != '{')
                return ch <= '{' && _category[ch] >= Q;
            int pos = startpos;
            while (--nChars > 0 && (ch = CharAt(++pos)) >= '0' && ch <= '9') ;
            if (nChars == 0 || pos - startpos == 1)
                return false;
            if (ch == '}')
                return true;
            if (ch != ',')
                return false;
            while (--nChars > 0 && (ch = CharAt(++pos)) >= '0' && ch <= '9') ;
            return nChars > 0 && ch == '}';
        }
 
        /*
         * Returns true for whitespace.
         */
        internal static bool IsSpace(char ch) {
            return(ch <= ' ' && _category[ch] == X);
        }
 
        /*
         * Returns true for chars that should be escaped.
         */
        internal static bool IsMetachar(char ch) {
            return(ch <= '|' && _category[ch] >= E);
        }
 
 
        /*
         * Add a string to the last concatenate.
         */
        internal void AddConcatenate(int pos, int cch, bool isReplacement) {
            RegexNode node;
 
            if (cch == 0)
                return;
 
            if (cch > 1) {
                String str = _pattern.Substring(pos, cch);
 
                if (UseOptionI() && !isReplacement) {
                    // We do the ToLower character by character for consistency.  With surrogate chars, doing
                    // a ToLower on the entire string could actually change the surrogate pair.  This is more correct
                    // linguistically, but since Regex doesn't support surrogates, it's more important to be 
                    // consistent. 
                    StringBuilder sb = new StringBuilder(str.Length);
                    for (int i=0; i<str.Length; i++)
                        sb.Append(Char.ToLower(str[i], _culture));
                    str = sb.ToString();
                }
 
                node = new RegexNode(RegexNode.Multi, _options, str);
            }
            else {
                char ch = _pattern[pos];
 
                if (UseOptionI() && !isReplacement)
                    ch = Char.ToLower(ch, _culture);
 
                node = new RegexNode(RegexNode.One, _options, ch);
            }
 
            _concatenation.AddChild(node);
        }
 
        /*
         * Push the parser state (in response to an open paren)
         */
        internal void PushGroup() {
            _group._next = _stack;
            _alternation._next = _group;
            _concatenation._next = _alternation;
            _stack = _concatenation;
        }
 
        /*
         * Remember the pushed state (in response to a ')')
         */
        internal void PopGroup() {
            _concatenation = _stack;
            _alternation = _concatenation._next;
            _group = _alternation._next;
            _stack = _group._next;
 
            // The first () inside a Testgroup group goes directly to the group
            if (_group.Type() == RegexNode.Testgroup && _group.ChildCount() == 0) {
                if (_unit == null)
                    throw MakeException(SR.GetString(SR.IllegalCondition));
 
                _group.AddChild(_unit);
                _unit = null;
            }
        }
 
        /*
         * True if the group stack is empty.
         */
        internal bool EmptyStack() {
            return _stack == null;
        }
 
        /*
         * Start a new round for the parser state (in response to an open paren or string start)
         */
        internal void StartGroup(RegexNode openGroup) {
            _group = openGroup;
            _alternation = new RegexNode(RegexNode.Alternate, _options);
            _concatenation = new RegexNode(RegexNode.Concatenate, _options);
        }
 
        /*
         * Finish the current concatenation (in response to a |)
         */
        internal void AddAlternate() {
            // The | parts inside a Testgroup group go directly to the group
 
            if (_group.Type() == RegexNode.Testgroup || _group.Type() == RegexNode.Testref) {
                _group.AddChild(_concatenation.ReverseLeft());
            }
            else {
                _alternation.AddChild(_concatenation.ReverseLeft());
            }
 
            _concatenation = new RegexNode(RegexNode.Concatenate, _options);
        }
 
        /*
         * Finish the current quantifiable (when a quantifier is not found or is not possible)
         */
        internal void AddConcatenate() {
            // The first (| inside a Testgroup group goes directly to the group
 
            _concatenation.AddChild(_unit);
            _unit = null;
        }
 
        /*
         * Finish the current quantifiable (when a quantifier is found)
         */
        internal void AddConcatenate(bool lazy, int min, int max) {
            _concatenation.AddChild(_unit.MakeQuantifier(lazy, min, max));
            _unit = null;
        }
 
        /*
         * Returns the current unit
         */
        internal RegexNode Unit() {
            return _unit;
        }
 
        /*
         * Sets the current unit to a single char node
         */
        internal void AddUnitOne(char ch) {
            if (UseOptionI())
                ch = Char.ToLower(ch, _culture);
 
            _unit = new RegexNode(RegexNode.One, _options, ch);
        }
 
        /*
         * Sets the current unit to a single inverse-char node
         */
        internal void AddUnitNotone(char ch) {
            if (UseOptionI())
                ch = Char.ToLower(ch, _culture);
 
            _unit = new RegexNode(RegexNode.Notone, _options, ch);
        }
 
        /*
         * Sets the current unit to a single set node
         */
        internal void AddUnitSet(string cc) {
            _unit = new RegexNode(RegexNode.Set, _options, cc);
        }
 
        /*
         * Sets the current unit to a subtree
         */
        internal void AddUnitNode(RegexNode node) {
            _unit = node;
        }
 
        /*
         * Sets the current unit to an assertion of the specified type
         */
        internal void AddUnitType(int type) {
            _unit = new RegexNode(type, _options);
        }
 
        /*
         * Finish the current group (in response to a ')' or end)
         */
        internal void AddGroup() {
            if (_group.Type() == RegexNode.Testgroup || _group.Type() == RegexNode.Testref) {
                _group.AddChild(_concatenation.ReverseLeft());
 
                if (_group.Type() == RegexNode.Testref && _group.ChildCount() > 2 || _group.ChildCount() > 3)
                    throw MakeException(SR.GetString(SR.TooManyAlternates));
            }
            else {
                _alternation.AddChild(_concatenation.ReverseLeft());
                _group.AddChild(_alternation);
            }
 
            _unit = _group;
        }
 
        /*
         * Saves options on a stack.
         */
        internal void PushOptions() {
            _optionsStack.Add(_options);
        }
 
        /*
         * Recalls options from the stack.
         */
        internal void PopOptions() {
            _options = _optionsStack[_optionsStack.Count - 1];
            _optionsStack.RemoveAt(_optionsStack.Count - 1);
        }
 
        /*
         * True if options stack is empty.
         */
        internal bool EmptyOptionsStack() {
            return(_optionsStack.Count == 0);
        }
 
        /*
         * Pops the option stack, but keeps the current options unchanged.
         */
        internal void PopKeepOptions() {
            _optionsStack.RemoveAt(_optionsStack.Count - 1);
        }
 
        /*
         * Fills in an ArgumentException
         */
        internal ArgumentException MakeException(String message) {
            return new ArgumentException(SR.GetString(SR.MakeException, _pattern, message));
        }
 
        /*
         * Returns the current parsing position.
         */
        internal int Textpos() {
            return _currentPos;
        }
 
        /*
         * Zaps to a specific parsing position.
         */
        internal void Textto(int pos) {
            _currentPos = pos;
        }
 
        /*
         * Returns the char at the right of the current parsing position and advances to the right.
         */
        internal char MoveRightGetChar() {
            return _pattern[_currentPos++];
        }
 
        /*
         * Moves the current position to the right. 
         */
        internal void MoveRight() {
            MoveRight(1);
        }
 
        internal void MoveRight(int i) {
            _currentPos += i;
        }
 
        /*
         * Moves the current parsing position one to the left.
         */
        internal void MoveLeft() {
            --_currentPos;
        }
 
        /*
         * Returns the char left of the current parsing position.
         */
        internal char CharAt(int i) {
            return _pattern[i];
        }
 
        /*
         * Returns the char right of the current parsing position.
         */
        internal char RightChar() {
            return _pattern[_currentPos];
        }
 
        /*
         * Returns the char i chars right of the current parsing position.
         */
        internal char RightChar(int i) {
            return _pattern[_currentPos + i];
        }
 
        /*
         * Number of characters to the right of the current parsing position.
         */
        internal int CharsRight() {
            return _pattern.Length - _currentPos;
        }
    }
}