private void substituteProductions(Dictionary <string, List <ProductionInfo> > productionsDict,
                                           ProductionInfo[] substitutes, // same LHS
                                           bool mixWithSource)
        {
            // this function is part of optimization of given production rules

            // we could have case, that sub production is marked and the one where there is replacement as well
            // in such case which marking to choose? so we don't allow substitutes to have markings
            if (substitutes.Any(it => it.IsMarked))
            {
                throw new ArgumentException();
            }

            string sub_lhs = substitutes.Select(it => it.LhsSymbol).Distinct().Single(); // making sure LHS symbol is the same

            Console.WriteLine("Substituting " + sub_lhs);

            foreach (string lhs in productionsDict.Keys.ToArray())
            {
                var replacements = new List <ProductionInfo>();

                foreach (ProductionInfo prod in productionsDict[lhs])
                {
                    // nothing to replace
                    if (!prod.RhsSymbols.Any(it => it.SymbolName.Equals(sub_lhs)))
                    {
                        replacements.Add(prod);
                    }
                    else
                    {
                        // -1 -- use original symbol, >=0 -- substitute (the value is the index of substitution)
                        IEnumerable <CycleCounter> counters = prod.RhsSymbols.ZipWithIndex()
                                                              .Select(it =>
                        {
                            bool hit = it.Item1.SymbolName.Equals(sub_lhs);
                            return(new CycleCounter(((!hit || mixWithSource) ? -1 : 0), (hit ? substitutes.Length : 0), it.Item2));
                        }).ToArray();

                        // we have initial run only in case if we mix substitutions with original production, otherwise it pure substitution
                        bool pass_first_as_source = mixWithSource;

                        do
                        {
                            if (pass_first_as_source)
                            {
                                pass_first_as_source = false;
                                if (!counters.All(it => it.Value == -1))
                                {
                                    throw new Exception("Oops, something wrong.");
                                }

                                // it is simply better to add original production instead of re-creating it from symbols
                                // after all, for every rhs symbol we would have -1 value, meaning "use original"
                                replacements.Add(prod);
                                continue;
                            }

                            var p = new ProductionInfo(prod.Position,
                                                       prod.LhsSymbol,
                                                       prod.Recursive,
                                                       counters.SyncZip(prod.RhsSymbols)
                                                       .Select(it => it.Item1.Value == -1 ? new[] { it.Item2 } : substitutes[it.Item1.Value].RhsSymbols).Flatten(),
                                                       prod.PassedMarkedWith);

                            // if there was no action code, no point of building proxy for it
                            if (prod.ActionCode != null)
                            {
                                FuncCallCode func_call = (FuncCallCode)(prod.ActionCode.Body);

                                // do not rename those parameters which have counter == -1
                                IEnumerable <Tuple <FuncParameter, int>[]> parameters = null;

                                parameters = counters.SyncZip(prod.ActionCode.Parameters)
                                             .Select(cit => cit.Item1.Value == -1 ? new[] { Tuple.Create(cit.Item2, cit.Item1.Index) }
                                          : substitutes[cit.Item1.Value].ActionCode.Parameters.Select(x => Tuple.Create(x, cit.Item1.Index)).ToArray())
                                             .ToArray();

                                // only subsituted parameters are renamed
                                Dictionary <Tuple <FuncParameter, int>, FuncParameter> param_map
                                    = FuncParameter.BuildParamMapping(parameters.Flatten());

                                p.ActionCode = CodeLambda.CreateProxy(lhs + "_sub__",
                                                                      // parameters
                                                                      parameters.Flatten().Select(it => param_map[it]),

                                                                      prod.ActionCode.ResultTypeName,
                                                                      functionsRegistry.Add(prod.ActionCode),

                                                                      // arguments
                                                                      counters.SyncZip(parameters)
                                                                      .Select(cit => cit.Item1.Value == -1 ? param_map[cit.Item2.Single()].NameAsCode()
                                                : new FuncCallCode(functionsRegistry.Add(substitutes[cit.Item1.Value].ActionCode),
                                                                   cit.Item2.Select(x => param_map[x].NameAsCode())))
                                                                      );
                            }
                            replacements.Add(p);
                        }while (counters.Iterate());
                    }
                }
                productionsDict[lhs] = replacements;
            }
        }