/// <summary>
        /// Compute the LR(1) closure including lookaheads in the item sets (page 261 of Compilers 2nd Ed.)
        /// </summary>
        private LrItemSet Lr1Closure(SymbolMetaDictionary symbolMeta, LrItemSet items)
        {
            // Initialize the return set to the item set
            var newset = new LrItemSet(items)
            {
                IsClosed = true
            };
            var toAdd         = new List <LrItem>();
            var newLookaheads = new HashSet <SymbolId>();

            do
            {
                toAdd.Clear();
                // For each item in the set with a marker before a nonterminal
                foreach (var item in newset.Where(i => (i.Marker < i.Length) && (this.symbols[i.Rule.RuleSymbolIds[i.Marker]] == SymbolKind.Nonterminal)))
                {
                    var nonterminal = item.Rule.RuleSymbolIds[item.Marker];
                    // Get all the possible lookaheads past this symbol
                    newLookaheads.Clear();
                    foreach (var lookahead in item.LookaheadIds)
                    {
                        var followingSymbols = item.Rule.RuleSymbolIds.Skip(item.Marker + 1).Append(lookahead);
                        newLookaheads.UnionWith(symbolMeta.FirstOfAll(followingSymbols));
                    }
                    if (newLookaheads.Any())
                    {
                        // For each rule of the production past the marker for this item
                        toAdd.AddRange(this.ProductionRulesBySymbol[nonterminal].Select(rule => new LrItem(rule, 0, false, newLookaheads)));
                    }
                }
            } while ((toAdd.Count > 0) && newset.MergeWith(toAdd));
            return(newset);
        }
        public LalrTable ComputeTable()
        {
            var symbolMeta = new SymbolMetaDictionary(this.symbols);

            symbolMeta.ComputeFirstFollowsAndNullable(this.Start, this.Eof, this.ProductionRules);
            var states      = this.ComputeItemSets(symbolMeta, out var acceptIndex);
            var gotoLookup  = this.ComputeGotoLookup(symbolMeta, states);
            var actionTable = new Dictionary <StateKey <SymbolId>, LalrAction>();

            foreach (var state in states)
            {
                foreach (var sym in this.symbols)
                {
                    var key = new StateKey <SymbolId>(state.Index, sym.Key);
                    if (sym.Value == SymbolKind.Terminal)
                    {
                        foreach (var item in state)
                        {
                            if ((item.Marker < item.Length) && (item.Rule.RuleSymbolIds[item.Marker] == sym.Key))
                            {
                                if (gotoLookup.TryGetValue(key, out var gotoState))
                                {
                                    actionTable[key] = new ShiftAction(gotoState);
                                }
                            }
                            else if (item.Length == item.Marker)
                            {
                                if (item.Rule.Index == acceptIndex)
                                {
                                    if (sym.Key == this.Eof)
                                    {
                                        actionTable[key] = new AcceptAction();
                                    }
                                }
                                else if (item.LookaheadIds.Contains(sym.Key) && (item.Rule.ProductionSymbolId != this.Init))
                                {
                                    if (actionTable.TryGetValue(key, out var action))
                                    {
                                        // Reduce-reduce conflict - plain LALR parser will fail, GLR parsers will try both rules in parallel
                                        actionTable[key] = ((ReduceAction)action).AddProductionRule(item.Rule);
                                    }
                                    else
                                    {
                                        actionTable[key] = new ReduceSingleAction(item.Rule);
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        if (gotoLookup.TryGetValue(key, out var gotoState))
                        {
                            actionTable[key] = new GotoAction(gotoState);
                        }
                    }
                }
            }
            return(new LalrTable(states.StartState.Index, actionTable, this.ProductionRules));
        }
        private Dictionary <StateKey <SymbolId>, int> ComputeGotoLookup(SymbolMetaDictionary symbolMeta, LrItemSetCollection itemSets)
        {
            var gotos            = new Dictionary <StateKey <SymbolId>, int>();
            var itemSetByKernels = itemSets.ToDictionary(s => s.Kernels.ToArray(), s => s, SetEqualityComparer <LrItem> .Default);

            foreach (var itemSet in itemSets)
            {
                foreach (var sym in symbolMeta.Keys)
                {
                    // Compute dynamic GOTO
                    var gotoClosure = this.Lr1Closure(symbolMeta, new LrItemSet(itemSet
                                                                                .Where(item => (item.Marker < item.Length) && item.Rule.RuleSymbolIds[item.Marker].Equals(sym))
                                                                                .Select(item => new LrItem(item.Rule, item.Marker + 1, true))));
                    if (!gotoClosure.Any())
                    {
                        continue;
                    }
                    var key = new StateKey <SymbolId>(itemSet.Index, sym);
                    if (gotos.ContainsKey(key))
                    {
                        continue;
                    }
                    // Match the dynamic GOTO to an actual state
                    if (itemSetByKernels.TryGetValue(gotoClosure.Kernels, out var existingGoto))
                    {
                        gotos.Add(key, existingGoto.Index);
                    }
                }
            }
            return(gotos);
        }
        private LrItemSetCollection ComputeItemSets(SymbolMetaDictionary symbolMeta, out int acceptIndex)
        {
            this.ComputeLr0ItemSetKernelsAndGotoLookup(out var itemSets, out var gotoSymbol, out acceptIndex);
            var dummyLookahead = new[] { this.Unknown };

            itemSets.StartState.Single().LookaheadIds.Add(this.Eof);
            var rulePropagations = new Dictionary <StateKey <LrItem>, HashSet <StateKey <LrItem> > >();

            foreach (var itemSet in itemSets)
            {
                var gotosForState = gotoSymbol[itemSet.Index];
                foreach (var kernelItem in itemSet)
                {
                    var itemKey = new StateKey <LrItem>(itemSet.Index, kernelItem);
                    // Create an item set with a dummy lookahead, based on the current item set
                    var dummyItem    = new LrItem(kernelItem.Rule, kernelItem.Marker, kernelItem.Marker > 0, dummyLookahead);
                    var dummyItemSet = new LrItemSet(dummyItem.Yield());
                    var j            = this.Lr1Closure(symbolMeta, dummyItemSet);
                    foreach (var gotoForState in gotosForState)
                    {
                        var gotoItemSet    = itemSets[gotoForState.Value];
                        var gotoItemLookup = gotoItemSet.ToDictionary(g => g, g => g);
                        // Find the items in the dummy set with the marker before this symbol
                        foreach (var b in j.Where(bb => (bb.Marker < bb.Length) && bb.Rule.RuleSymbolIds[bb.Marker].Equals(gotoForState.Key)))
                        {
                            // Get the item corresponding to the goto state with the marker past the current symbol
                            var gotoItem = gotoItemLookup[new LrItem(b.Rule, b.Marker + 1, true)];
                            if (b.LookaheadIds.Any(l => l == this.Unknown))
                            {
                                if (!rulePropagations.ContainsKey(itemKey))
                                {
                                    rulePropagations[itemKey] = new HashSet <StateKey <LrItem> >();
                                }
                                rulePropagations[itemKey].Add(new StateKey <LrItem>(gotoItemSet.Index, gotoItem));
                            }
                            gotoItem.LookaheadIds.UnionWith(b.LookaheadIds.Where(l => l != this.Unknown));
                        }
                    }
                }
            }
            bool changed;

            do
            {
                changed = false;
                foreach (var itemSet in itemSets)
                {
                    foreach (var item in itemSet)
                    {
                        var itemKey = new StateKey <LrItem>(itemSet.Index, item);
                        if (!rulePropagations.TryGetValue(itemKey, out var propagated))
                        {
                            continue;
                        }
                        foreach (var key in propagated)
                        {
                            if (key.Value.LookaheadIds.AddRange(item.LookaheadIds))
                            {
                                changed = true;
                            }
                        }
                    }
                }
            } while (changed);
            // Close all kernels
            for (var i = 0; i < itemSets.Count; i++)
            {
                itemSets[i]       = this.Lr1Closure(symbolMeta, itemSets[i]);
                itemSets[i].Index = i;
            }
            return(itemSets);
        }