public static cProductInfo[] cm_getProductsByRoot(cLexem a_root, ref int a_Counter)
        {
            List<cProductInfo> _retLst = new List<cProductInfo>();
            int _counterRoots = a_Counter;
            a_Counter += a_root.cp_ListProducts.Count;

            if (a_root.cp_ListProducts.ContainsKey(cLexem.cc_EpsilonLexem))
                a_Counter--;

            foreach (KeyValuePair<cLexem, List<cLexem>> _kvp in a_root.cp_ListProducts)
            {
                // В список не добавляются пустые продукции
                if (_kvp.Key.cp_Type == eLexType.Epsilon)
                    continue;

                cProductInfo _newPro = new cProductInfo();

                _newPro.cf_root = new KeyValuePair<cLexem,int>(a_root, _counterRoots++);

                _newPro.cf_lstLexems = new List<KeyValuePair<cLexem, int>>();
                int i = _kvp.Value.Count;
                foreach (cLexem _lex in _kvp.Value)
                {
                    _newPro.cf_lstLexems.Add(new KeyValuePair<cLexem, int>(_lex, a_Counter++));

                    // Для последнего нетерминала
                    if (--i == 0)
                    {
                        bool _flag = false;
                        if (_lex.cp_Type == eLexType.NonTerminal)
                        {
                            foreach (cLexem _leadLex in _lex.cp_LeadLexems)
                                if (_leadLex.cp_Type == eLexType.Epsilon)
                                {
                                    _flag = true;
                                    break;
                                }
                        }
                        if (_flag)
                        {
                            a_Counter++;
                        }
                    }
                }

                if (_kvp.Key.cp_Type == eLexType.NonTerminal)
                    _newPro.cf_arrDirLexems = _newPro.cf_root.Key.cm_GetLeadLexems(_kvp.Key);
                else
                    _newPro.cf_arrDirLexems = new cLexem[]{ _kvp.Key };

                _retLst.Add(_newPro);
            }
            return _retLst.ToArray();
        }
        DataTable cm_fillDataTable(cProductInfo[] a_data)
        {
            cm_checkProducts(a_data);
            Dictionary<cLexem, int> _dicJump = new Dictionary<cLexem, int>();
            for (int k = 0; k < a_data.Length; k++)
            {
                if (!_dicJump.ContainsKey(a_data[k].cp_Root.Key))
                    _dicJump.Add(a_data[k].cp_Root.Key, a_data[k].cp_Root.Value);
            }

            DataTable _retDT = new DataTable("table");
            _retDT.Columns.Add("i", typeof(int));
            _retDT.Columns.Add("terminals", typeof(string[]) /*typeof(object)*/);
            _retDT.Columns.Add("jump", typeof(int));
            _retDT.Columns.Add("accept", typeof(bool));
            _retDT.Columns.Add("stack", typeof(bool));
            _retDT.Columns.Add("return", typeof(bool));
            _retDT.Columns.Add("error", typeof(bool));
            _retDT.Columns.Add("action", typeof(string));

            int _count = a_data.Length;
            bool _warning = false;
            for (int k = 0; k < _count; k++)
            {
                cProductInfo _info = a_data[k];

                // Проверка LL(1)
                if (k == 0 || a_data[k - 1].cp_Root.Key != _info.cp_Root.Key)
                {
                    _warning = _info.cp_Root.Key.cm_HasEpsilonProduct();
                }
                foreach (cLexem _lex in _info.cp_ArrDirLexems)
                {
                    if (cf_follow[_info.cp_Root.Key].ContainsKey(_lex))
                    {
                        if (_warning)
                            throw new Exception("Невозможно однозначно определить переход из-за " + cLexem.cc_Epsilon + "-порождающей продукции в " + _info.cp_Root.Key + ".");
                         if (_lex.cp_Type != eLexType.Epsilon)
                            _warning = true;
                    }
                    else if (_lex.cp_Type == eLexType.Epsilon)
                    {
                        if (_warning)
                            throw new Exception("Невозможно однозначно определить переход из-за " + cLexem.cc_Epsilon + "-порождающей продукции в " + _info.cp_Root.Key + ".");
                        _warning = true;
                    }
                }

                int _i = _info.cp_Root.Value;
                cSet<string> _terminals = new cSet<string>();
                foreach (cLexem _lex in _info.cp_ArrDirLexems)
                    if (_lex.cp_Type == eLexType.Terminal)
                        _terminals.Add(_lex.cf_Name);
                int _jump = _info.cp_ArrLexems[0].Value;
                bool _accept = false;
                bool _stack = false;
                bool _return = false;
                bool _error = false;
                if (k == _count - 1 || a_data[k + 1].cp_Root.Key != _info.cp_Root.Key)
                    _error = true;
                string _action = "";
                //_terminals.Sort();
                _retDT.Rows.Add(_i, _terminals.ToArray(), _jump, _accept, _stack, _return, _error, _action);

                KeyValuePair<cLexem, int>[] _arrLexems = _info.cp_ArrLexems;
                int _lexCount = _arrLexems.Length;
                for (int j = 0; j < _lexCount; j++)
                {
                    cLexem _lexem = _arrLexems[j].Key;
                    _i = _arrLexems[j].Value;

                    // ERROR
                    _error = true;

                    // TERMINALS
                    switch (_lexem.cp_Type)
                    {
                        case eLexType.Epsilon:
                            _terminals = new cSet<string>();
                            //foreach (cLexem _lex in _info.cp_ArrDirLexems)
                            //    _terminals.Add(_lex.cf_Name);
                            break;
                        case eLexType.Terminal:
                            _terminals = new cSet<string>();
                            _terminals.Add(_lexem.cf_Name);
                            break;
                        case eLexType.NonTerminal:
                            _terminals = new cSet<string>();
                            foreach (cLexem _lex in _lexem.cp_LeadLexems)
                                if (_lex.cp_Type == eLexType.Epsilon)
                                {
                                    if (j == _lexCount - 1)
                                    {
                                        _retDT.Rows.Add(_i + 1, new string[0], 0, false, false, true, true, "");
                                    }
                                    _error = false;
                                }
                                else
                                    _terminals.Add(_lex.cf_Name);
                            break;
                        case eLexType.Action:
                            _terminals = new cSet<string>();
                            break;
                    }

                    // JUMP
                    if (_lexem.cp_Type == eLexType.NonTerminal)
                    {
                        _jump = _dicJump[_lexem];
                    }
                    else
                    {
                        // Не последний терминал или действие
                        //if (j < _lexCount - 1)
                            _jump = _i + 1;
                        // Последний терминал или действие
                        //else
                        //    _jump = 0;
                    }

                    // ACCEPT
                    _accept = _lexem.cp_Type == eLexType.Terminal;

                    // STACK
                    // Не последний нетерминал правой части
                    _stack = (_lexem.cp_Type == eLexType.NonTerminal && j < _lexCount - 1);

                    // RETURN
                    // Крайний правый терминал или действие
                    _return = ((_lexem.cp_Type != eLexType.NonTerminal) && j == _lexCount - 1);

                    // ACTION
                    _action = "";
                    if (_lexem.cp_Type == eLexType.Action)
                        _action = _lexem.cf_Name;

                    //_terminals.Sort();
                    _retDT.Rows.Add(_i, _terminals.ToArray(), _jump, _accept, _stack, _return, _error, _action);
                }
            }

            // Сортировка строк
            DataTable _oldDT = _retDT;
            _retDT = _oldDT.Clone();

            foreach (DataRow _dr in _oldDT.Select(null, "i"))
                _retDT.Rows.Add(_dr.ItemArray);

            return _retDT;
        }
        void cm_checkProducts(cProductInfo[] a_data)
        {
            int _count = a_data.Length;
            Dictionary<cLexem, cLexem> _dic = new Dictionary<cLexem, cLexem>();
            for (int k = 0; k < _count; k++)
            {
                cProductInfo _info = a_data[k];
                KeyValuePair<cLexem, int>[] _arrLexems = _info.cp_ArrLexems;
                int _lexCount = _arrLexems.Length;
                bool _clearDic = true;
                for (int j = 0; j < _lexCount; j++)
                {
                    if (_clearDic)
                        _dic.Clear();
                    _clearDic = true;
                    cLexem _lexem = _arrLexems[j].Key;

                    switch (_lexem.cp_Type)
                    {
                        case eLexType.Terminal:
                            if (_dic.ContainsKey(_lexem))
                                throw new cNotLL1Exception(cLexem.cc_EpsilonLexem, cLexem.cc_EpsilonLexem, "Невозможно однозначно определить продукцию для символа " + _lexem.ToString() + " правой части " + _info.cp_Root.Key.ToString() + " из-за " + cLexem.cc_Epsilon + "-порождающих нетерминалов.");
                            _dic.Add(_lexem, _lexem);
                            break;
                        case eLexType.NonTerminal:
                            foreach (cLexem _lex in _lexem.cp_LeadLexems)
                            {
                                if (_lex.cp_Type == eLexType.Terminal)
                                {
                                    if (_dic.ContainsKey(_lex))
                                        throw new cNotLL1Exception(cLexem.cc_EpsilonLexem, cLexem.cc_EpsilonLexem, "Невозможно однозначно определить продукцию для символа " + _lex.ToString() + " правой части " + _info.cp_Root.Key.ToString() + " из-за " + cLexem.cc_Epsilon + "-порождающих нетерминалов.");
                                    _dic.Add(_lex, _lex);
                                }
                                else if (_lex.cp_Type == eLexType.Epsilon)
                                {
                                    _clearDic = false;
                                }

                            }
                            break;
                        case eLexType.Epsilon:
                            _clearDic = true;
                            break;
                    }
                }
            }
        }