/// <summary>
        /// Imperative implementation of chain, which in a non-stack overflow utopia
        /// would look similar to this:
        ///
        ///     from x in ps[index]
        ///     from y in chaini(ps, index + 1)
        ///     select x.Cons(y);
        ///
        /// </summary>
        public static Parser <Seq <T> > chaini <T>(Seq <Parser <T> > ps) =>
        ps.IsEmpty
                ? unexpected <Seq <T> >("chain parser with 0 items")
                : inp =>
        {
            if (ps.Count == 1)
            {
                return(ps.Head.Map(x => x.Cons())(inp));
            }

            var              current = inp;
            List <T>         results = new List <T>();
            ParserError      error   = null;
            ParserResult <T> last    = null;
            int              count   = ps.Count;

            foreach (var p in ps)
            {
                count--;
                var t = p(current);

                if (last == null)
                {
                    // cerr
                    if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.Error)
                    {
                        return(ConsumedError <Seq <T> >(t.Reply.Error));
                    }
                    // eerr
                    else if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.Error)
                    {
                        return(EmptyError <Seq <T> >(t.Reply.Error));
                    }
                    // c*k
                    else if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.OK)
                    {
                        results.Add(t.Reply.Result);
                        last    = t;
                        error   = t.Reply.Error;
                        current = t.Reply.State;
                    }
                    // eok
                    else     //if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.OK)
                    {
                        results.Add(t.Reply.Result);
                        last  = t;
                        error = t.Reply.Error;
                    }
                }
                else
                {
                    if (last.Tag == ResultTag.Consumed)
                    {
                        // c*k, cerr
                        if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.Error)
                        {
                            return(ConsumedError <Seq <T> >(t.Reply.Error));
                        }
                        // c*k, eerr
                        else if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.Error)
                        {
                            return(ConsumedError <Seq <T> >(mergeError(error, t.Reply.Error)));
                        }
                        // c*k, c*k
                        else if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.OK)
                        {
                            if (count == 0)
                            {
                                results.Add(t.Reply.Result);
                                return(ConsumedOK(Seq(results), t.Reply.State, t.Reply.Error));
                            }
                            else
                            {
                                results.Add(t.Reply.Result);
                                last    = t;
                                error   = t.Reply.Error;
                                current = t.Reply.State;
                            }
                        }
                        // c*k, eok
                        else     //if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.OK)
                        {
                            if (count == 0)
                            {
                                // c*k, eok -> c*k  (not a typo, this should be -> c*k)
                                results.Add(t.Reply.Result);
                                return(ConsumedOK(Seq(results), t.Reply.State, mergeError(error, t.Reply.Error)));
                            }
                            else
                            {
                                results.Add(t.Reply.Result);
                                last  = t;
                                error = mergeError(error, t.Reply.Error);
                            }
                        }
                    }
                    else if (last.Tag == ResultTag.Empty)
                    {
                        // eok, cerr
                        if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.Error)
                        {
                            return(ConsumedError <Seq <T> >(t.Reply.Error));
                        }
                        // eok, eerr
                        else if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.Error)
                        {
                            return(EmptyError <Seq <T> >(mergeError(error, t.Reply.Error)));
                        }
                        // eok, c*k
                        else if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.OK)
                        {
                            if (count == 0)
                            {
                                results.Add(t.Reply.Result);
                                return(ConsumedOK(Seq(results), t.Reply.State, t.Reply.Error));
                            }
                            else
                            {
                                results.Add(t.Reply.Result);
                                last    = t;
                                error   = t.Reply.Error;
                                current = t.Reply.State;
                            }
                        }
                        // eok, eok
                        else     //if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.OK)
                        {
                            if (count == 0)
                            {
                                results.Add(t.Reply.Result);
                                return(EmptyOK(Seq(results), t.Reply.State, mergeError(error, t.Reply.Error)));
                            }
                            else
                            {
                                results.Add(t.Reply.Result);
                                last  = t;
                                error = mergeError(error, t.Reply.Error);
                            }
                        }
                    }
                }
            }
            return(ConsumedOK(Seq(results), current, error));
        };
        /// <summary>
        /// Imperative implementation of chain, which in a non-stack overflow utopia
        /// would look similar to this:
        ///
        ///     from x in ps[index]
        ///     from y in chaini(ps, index + 1)
        ///     select x.Cons(y);
        ///
        /// </summary>
        public static Parser <I, IEnumerable <O> > chaini <I, O>(Parser <I, O>[] ps) =>
        ps.Length == 0
                ? unexpected <I, IEnumerable <O> >("chain parser with 0 items")
                : inp =>
        {
            if (ps.Length == 1)
            {
                return(ps[0].Map(x => new[] { x }.AsEnumerable())(inp));
            }

            var                 current = inp;
            var                 results = new List <O>();
            ParserError         error   = null;
            ParserResult <I, O> last    = null;
            int                 count   = ps.Length;

            foreach (var p in ps)
            {
                count--;
                var t = p(current);

                if (last == null)
                {
                    // cerr
                    if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.Error)
                    {
                        return(ConsumedError <I, IEnumerable <O> >(t.Reply.Error));
                    }
                    // eerr
                    else if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.Error)
                    {
                        return(EmptyError <I, IEnumerable <O> >(t.Reply.Error));
                    }
                    // c*k
                    else if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.OK)
                    {
                        results.Add(t.Reply.Result);
                        last    = t;
                        error   = t.Reply.Error;
                        current = t.Reply.State;
                    }
                    // eok
                    else     //if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.OK)
                    {
                        results.Add(t.Reply.Result);
                        last  = t;
                        error = t.Reply.Error;
                    }
                }
                else
                {
                    if (last.Tag == ResultTag.Consumed)
                    {
                        // c*k, cerr
                        if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.Error)
                        {
                            return(ConsumedError <I, IEnumerable <O> >(t.Reply.Error));
                        }
                        // c*k, eerr
                        else if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.Error)
                        {
                            return(ConsumedError <I, IEnumerable <O> >(mergeError(error, t.Reply.Error)));
                        }
                        // c*k, c*k
                        else if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.OK)
                        {
                            if (count == 0)
                            {
                                results.Add(t.Reply.Result);
                                return(ConsumedOK <I, IEnumerable <O> >(results, t.Reply.State, t.Reply.Error));
                            }
                            else
                            {
                                results.Add(t.Reply.Result);
                                last    = t;
                                error   = t.Reply.Error;
                                current = t.Reply.State;
                            }
                        }
                        // c*k, eok
                        else     //if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.OK)
                        {
                            if (count == 0)
                            {
                                // c*k, eok -> c*k  (not a typo, this should be -> c*k)
                                results.Add(t.Reply.Result);
                                return(ConsumedOK <I, IEnumerable <O> >(results, t.Reply.State, mergeError(error, t.Reply.Error)));
                            }
                            else
                            {
                                results.Add(t.Reply.Result);
                                last  = t;
                                error = mergeError(error, t.Reply.Error);
                            }
                        }
                    }
                    else if (last.Tag == ResultTag.Empty)
                    {
                        // eok, cerr
                        if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.Error)
                        {
                            return(ConsumedError <I, IEnumerable <O> >(t.Reply.Error));
                        }
                        // eok, eerr
                        else if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.Error)
                        {
                            return(EmptyError <I, IEnumerable <O> >(mergeError(error, t.Reply.Error)));
                        }
                        // eok, c*k
                        else if (t.Tag == ResultTag.Consumed && t.Reply.Tag == ReplyTag.OK)
                        {
                            if (count == 0)
                            {
                                results.Add(t.Reply.Result);
                                return(ConsumedOK <I, IEnumerable <O> >(results, t.Reply.State, t.Reply.Error));
                            }
                            else
                            {
                                results.Add(t.Reply.Result);
                                last    = t;
                                error   = t.Reply.Error;
                                current = t.Reply.State;
                            }
                        }
                        // eok, eok
                        else     //if (t.Tag == ResultTag.Empty && t.Reply.Tag == ReplyTag.OK)
                        {
                            if (count == 0)
                            {
                                results.Add(t.Reply.Result);
                                return(EmptyOK <I, IEnumerable <O> >(results, t.Reply.State, mergeError(error, t.Reply.Error)));
                            }
                            else
                            {
                                results.Add(t.Reply.Result);
                                last  = t;
                                error = mergeError(error, t.Reply.Error);
                            }
                        }
                    }
                }
            }
            return(ConsumedOK <I, IEnumerable <O> >(results, current, error));
        };