public static Tuple <int, List <string>, string> FeedbackForWhileToTM <S>(WExpr correctProgram, TMCB <int, S> attemptTM, int maxGrade)
        {
            //returns grade, feedback strings, and a sample input for the tape simulator
            TMCB <int, int> correctTM      = correctProgram.ToTMCB(-1);
            int             numTapes       = correctProgram.GetNumVariables();
            const int       numInputs      = 100;
            const int       maxWrongOutpus = 5; //max number of wrong outputs the user is shown.

            //<correctTM> and <attemptTM> are compared by running both on a set of test inputs and comparing the outputs.
            int           inputsPerTape   = (int)Math.Pow(numInputs, (double)1 / numTapes);
            List <string> wrongOutputList = new List <string>();
            string        sampleInput     = "";

            int  countCorrectOutputs = 0;
            int  countWrongOutpus    = 0;
            bool dummy;

            foreach (int[][] input in WhileUtilities.NonNegIntTestInputs(numTapes, inputsPerTape, correctProgram.GetUselessVariables().ToArray()))
            {
                int[][] correctOutput = correctTM.Run(input, out dummy);

                //<correctTM> uses LSBF encoding, <attemptTM> uses MSBF encoding. Reverse <input> for <attemptTM>.
                for (int i = 0; i < input.Length; ++i)
                {
                    input[i] = input[i].Reverse().ToArray();
                }

                int[][] attemptOutput = attemptTM.Run(input, out dummy);

                //for comparing the outputs, reverse <correctOutput>
                for (int i = 0; i < correctOutput.Length; ++i)
                {
                    correctOutput[i] = correctOutput[i].Reverse().ToArray();
                }

                //get string representations of the outputs
                string[] correctStrings = Array.ConvertAll(correctOutput, tape => WhileUtilities.TapeToString(tape, correctTM.blank, new[] { 0 }, "_"));
                string[] attemptStrings = Array.ConvertAll(attemptOutput, tape => WhileUtilities.TapeToString(tape, attemptTM.blank, new[] { 0 }, "_"));

                if (WhileUtilities.TapesEqual(correctStrings, attemptStrings))
                {
                    ++countCorrectOutputs;
                }
                else
                {
                    if (countWrongOutpus == 0)
                    {
                        //first wrong output: corresponding input is stored in <sampleInput>
                        string[] tapes = Array.ConvertAll(input, tape => WhileUtilities.TapeToString(tape, correctTM.blank, new[] { 0 }, "_"));
                        sampleInput = string.Join(",", tapes);
                    }
                    ++countWrongOutpus;
                    if (countWrongOutpus <= maxWrongOutpus)
                    {
                        wrongOutputList.Add(string.Empty);
                        wrongOutputList.Add($"Input #{countWrongOutpus}:");
                        for (int i = 0; i < input.Length; ++i)
                        {
                            wrongOutputList.Add($"tape {i}: {WhileUtilities.TapeToString(input[i], correctTM.blank, null)}");
                        }
                        wrongOutputList.Add("Your output:");
                        for (int i = 0; i < attemptStrings.Length; ++i)
                        {
                            wrongOutputList.Add($"tape {i}: {attemptStrings[i]}");
                        }
                        wrongOutputList.Add("Correct output:");
                        for (int i = 0; i < correctStrings.Length; ++i)
                        {
                            wrongOutputList.Add($"tape {i}: {correctStrings[i]}");
                        }
                    }
                }
            }

            double        actualInputs = Math.Pow(inputsPerTape, numTapes);
            int           grade        = (int)((countCorrectOutputs / actualInputs) * maxGrade);
            List <string> feedbackList = new List <string>();

            if (countWrongOutpus == 0)
            {
                feedbackList.Add("Correct!");
            }
            else
            {
                if (countWrongOutpus <= maxWrongOutpus)
                {
                    feedbackList.Add($"{countWrongOutpus} of {actualInputs} outpus were wrong:");
                }
                else
                {
                    feedbackList.Add($"{countWrongOutpus} of {actualInputs} outpus were wrong. Here are {maxWrongOutpus} of them:");
                }
                foreach (string line in wrongOutputList)
                {
                    feedbackList.Add(line);
                }
            }

            return(new Tuple <int, List <string>, string>(grade, feedbackList, sampleInput));
        }
        //public static TMCB<char, string> ParseTMFromXML(XElement automaton_wrapped)
        //{
        //    var Q = new HashSet<State<string>>();
        //    var F = new HashSet<State<string>>();
        //    State<string> q0 = null;
        //    var Sigma = new HashSet<char>();
        //    var Gamma = new HashSet<char>();
        //    char blank = '?';
        //    //var Delta = new Dictionary<TwoTuple<State<string>, string>, ThreeTuple<State<string>, string, string>>();

        //    var IdStateDict = new Dictionary<int, State<string>>();

        //    XElement automaton = XElement.Parse(RemoveAllNamespaces(automaton_wrapped.ToString()));

        //    //Parse Q:
        //    XElement stateSet = automaton.Element("stateSet");
        //    foreach (XElement child in stateSet.Elements("state"))
        //    {
        //        try
        //        {
        //            int id = Int32.Parse(child.Element("id").Value);
        //            string label = child.Element("label").Value;
        //            var q = new State<string>(id, label);
        //            Q.Add(q);
        //            IdStateDict.Add(id, q);
        //        }
        //        catch (NullReferenceException e)
        //        {
        //            int x = 0;
        //        }
        //    }

        //    //Parse F:
        //    XElement acceptingSet = automaton.Element("acceptingSet");
        //    foreach (XElement child in acceptingSet.Elements("state"))
        //    {
        //        State<string> q;
        //        int id = (int)child.Attribute("sid");
        //        if (!IdStateDict.TryGetValue(id, out q))
        //        {
        //            //TODO: Throw even more exceptions
        //        }
        //        F.Add(q);
        //    }

        //    //Parse Sigma and Gamma:
        //    XElement alphabet = automaton.Element("alphabet");
        //    foreach (XElement child in alphabet.Elements("symbol"))
        //    {
        //        char symbol = Convert.ToChar(child.Value);
        //        Gamma.Add(symbol);
        //        if (symbol != blank)
        //            Sigma.Add(Convert.ToChar(child.Value));
        //    }

        //    TMCB<char, string> rv = new TMCB<char, string>(Q, F, Sigma, Gamma, blank);

        //    //Parse q0:
        //    //TODO: exception for multiple initial states
        //    XElement initState = automaton.Element("initState");
        //    foreach (XElement child in initState.Elements("state"))
        //    {
        //        int id = (int)child.Attribute("sid");
        //        if (!IdStateDict.TryGetValue(id, out q0))
        //        {
        //            //TODO: throw exception
        //        }
        //        else
        //        {
        //            rv.q0 = q0;
        //        }
        //    }

        //    //Parse Delta:
        //    XElement transitionSet = automaton.Element("transitionSet");
        //    foreach (XElement child in transitionSet.Elements("transition"))
        //    {
        //        int from_id = Int32.Parse(child.Element("from").Value);
        //        int to_id = Int32.Parse(child.Element("to").Value);
        //        string read = Convert.ToString(child.Element("read").Value);
        //        string write = Convert.ToString(child.Element("write").Value);
        //        string head = Convert.ToString(child.Element("head").Value);

        //        State<string> q1, q2;
        //        if (!IdStateDict.TryGetValue(from_id, out q1) | !IdStateDict.TryGetValue(to_id, out q2))
        //        {
        //            //TODO: throw exception
        //        }
        //        else
        //        {
        //            //only works if read, write and head have the same lengths
        //            Transition<char, string> t = new Transition<char, string>();
        //            for (int i = 0; i < read.Length; ++i)
        //            {
        //                char readSymbol = read[i];
        //                char writeSymbol = write[i];
        //                char headSymbol = head[i];
        //                t.AddReadCondition(i, read[i]);

        //                //only add write action if the symbol actually gets changed
        //                //only add move action if the head actually moves
        //                if (readSymbol != writeSymbol)
        //                {
        //                    t.AddWriteAction(i, write[i]);
        //                }
        //                if (headSymbol == 'R')
        //                {
        //                    t.AddMoveAction(i, Directions.Right);
        //                }
        //                if (headSymbol == 'L')
        //                {
        //                    t.AddMoveAction(i, Directions.Left);
        //                }
        //            }

        //            //only set target state if it is different from the source state
        //            if (q1 != q2)
        //            {
        //                t.TargetState = q2;
        //            }

        //            rv.AddTransition(q1, t);
        //        }
        //    }

        //    return rv;
        //}
        #endregion

        //not yet tested at all
        #region <int, string>
        public static TMCB <int, string> ParseTMFromXML(XElement automaton_wrapped)
        {
            var            Q     = new HashSet <State <string> >();
            var            F     = new HashSet <State <string> >();
            State <string> q0    = null;
            var            Sigma = new HashSet <int>();
            var            Gamma = new HashSet <int>();
            int            blank = -1;
            //var Delta = new Dictionary<TwoTuple<State<string>, string>, ThreeTuple<State<string>, string, string>>();

            var IdStateDict = new Dictionary <int, State <string> >();

            XElement automaton = XElement.Parse(RemoveAllNamespaces(automaton_wrapped.ToString()));

            //Parse Q:
            XElement stateSet = automaton.Element("stateSet");

            foreach (XElement child in stateSet.Elements("state"))
            {
                try
                {
                    int    id    = Int32.Parse(child.Element("id").Value);
                    string label = child.Element("label").Value;
                    var    q     = new State <string>(id, label);
                    Q.Add(q);
                    IdStateDict.Add(id, q);
                }
                catch (NullReferenceException e)
                {
                    int x = 0;
                }
            }

            //Parse F:
            XElement acceptingSet = automaton.Element("acceptingSet");

            foreach (XElement child in acceptingSet.Elements("state"))
            {
                State <string> q;
                int            id = (int)child.Attribute("sid");
                if (!IdStateDict.TryGetValue(id, out q))
                {
                    //TODO: Throw even more exceptions
                }
                F.Add(q);
            }

            //Parse Sigma and Gamma:
            XElement alphabet = automaton.Element("alphabet");

            foreach (XElement child in alphabet.Elements("symbol"))
            {
                char symbol = Convert.ToChar(child.Value);
                int  intSymbol;
                if (!charToInt(symbol, blank, out intSymbol))
                {
                    //TODO: exception
                }
                Gamma.Add(intSymbol);
                if (intSymbol != blank)
                {
                    Sigma.Add(intSymbol);
                }
            }

            TMCB <int, string> rv = new TMCB <int, string>(Q, F, Sigma, Gamma, blank);

            //Parse q0:
            //TODO: exception for multiple initial states
            XElement initState = automaton.Element("initState");

            foreach (XElement child in initState.Elements("state"))
            {
                int id = (int)child.Attribute("sid");
                if (!IdStateDict.TryGetValue(id, out q0))
                {
                    //TODO: throw exception
                }
                else
                {
                    rv.q0 = q0;
                }
            }

            //Parse Delta:
            XElement transitionSet = automaton.Element("transitionSet");

            foreach (XElement child in transitionSet.Elements("transition"))
            {
                int    from_id = Int32.Parse(child.Element("from").Value);
                int    to_id   = Int32.Parse(child.Element("to").Value);
                string read    = Convert.ToString(child.Element("read").Value);
                string write   = Convert.ToString(child.Element("write").Value);
                string head    = Convert.ToString(child.Element("head").Value);

                State <string> q1, q2;
                if (!IdStateDict.TryGetValue(from_id, out q1) | !IdStateDict.TryGetValue(to_id, out q2))
                {
                    //TODO: throw exception
                }
                else
                {
                    //only works if read, write and head have the same lengths
                    Transition <int, string> t = new Transition <int, string>();
                    for (int i = 0; i < read.Length; ++i)
                    {
                        int readSymbol;
                        if (!charToInt(read[i], blank, out readSymbol))
                        {
                            //TODO: exception
                        }
                        int writeSymbol;
                        if (!charToInt(write[i], blank, out writeSymbol))
                        {
                            //TODO: exception
                        }
                        char headSymbol = head[i];

                        t.AddReadCondition(i, readSymbol);

                        //only add write action if the symbol actually gets changed
                        //only add move action if the head actually moves
                        if (readSymbol != writeSymbol)
                        {
                            t.AddWriteAction(i, writeSymbol);
                        }
                        if (headSymbol == 'R')
                        {
                            t.AddMoveAction(i, Directions.Right);
                        }
                        if (headSymbol == 'L')
                        {
                            t.AddMoveAction(i, Directions.Left);
                        }
                    }

                    //only set target state if it is different from the source state
                    if (q1 != q2)
                    {
                        t.TargetState = q2;
                    }

                    rv.AddTransition(q1, t);
                }
            }

            return(rv);
        }