internal IMathAtom BuildTable(string environment, IMathList firstList, bool isRow, char stopChar)
        {
            var oldEnv = _currentEnvironment;

            _currentEnvironment = new TableEnvironmentProperties(environment);
            int currentRow    = 0;
            int currentColumn = 0;
            var rows          = new List <List <IMathList> > {
                new List <IMathList>()
            };

            if (firstList != null)
            {
                rows[currentRow].Add(firstList);
                if (isRow)
                {
                    _currentEnvironment.NRows++;
                    currentRow++;
                    rows.Add(new List <IMathList>());
                }
                else
                {
                    currentColumn++;
                }
            }
            while (HasCharacters && !(_currentEnvironment.Ended))
            {
                var list = BuildInternal(false, stopChar);
                if (list == null)
                {
                    return(null);
                }
                rows[currentRow].Add(list);
                currentColumn++;
                if (_currentEnvironment.NRows > currentRow)
                {
                    currentRow = _currentEnvironment.NRows;
                    rows.Add(new List <IMathList>());
                    currentColumn = 0;
                }
            }
            if (_currentEnvironment.Name != null && !_currentEnvironment.Ended)
            {
                SetError(@"Missing \end");
                return(null);
            }
            var tableResult = MathAtoms.Table(_currentEnvironment.Name, rows);

            if (tableResult.Error is string error)
            {
                SetError(error);
                return(null);
            }
            _currentEnvironment = oldEnv;
            return(tableResult.Value);
        }
        internal IMathAtom _BoundaryAtomForDelimiterType(string delimiterType)
        {
            string delim = ReadDelimiter();

            if (delim == null)
            {
                SetError("Missing delimiter for " + delimiterType);
                return(null);
            }
            var boundary = MathAtoms.BoundaryAtom(delim);

            if (boundary == null)
            {
                SetError(@"Invalid delimiter for \" + delimiterType + ": " + delim);
            }
            return(boundary);
        }
        public static string DelimiterToString(IMathAtom delimiter)
        {
            var command = MathAtoms.DelimiterName(delimiter);

            if (command == null)
            {
                return(string.Empty);
            }
            var singleChars = @"()[]<>|./";

            if (singleChars.IndexOf(command, StringComparison.OrdinalIgnoreCase) >= 0 && command.Length == 1)
            {
                return(command);
            }
            if (command == "||")
            {
                return(@"\|");
            }
            else
            {
                return(@"\" + command);
            }
        }
        public static string MathListToString(IMathList mathList)
        {
            var builder          = new StringBuilder();
            var currentFontStyle = FontStyle.Default;

            foreach (var atom in mathList)
            {
                if (currentFontStyle != atom.FontStyle)
                {
                    if (currentFontStyle != FontStyle.Default)
                    {
                        // close the previous font style
                        builder.Append("}");
                    }
                    if (atom.FontStyle != FontStyle.Default)
                    {
                        // open a new font style
                        var fontStyleName = atom.FontStyle.FontName();
                        builder.Append(@"\" + fontStyleName + "{");
                    }
                }
                currentFontStyle = atom.FontStyle;
                switch (atom.AtomType)
                {
                case MathAtomType.Fraction: {
                    var fraction    = (IFraction)atom;
                    var numerator   = MathListToString(fraction.Numerator);
                    var denominator = MathListToString(fraction.Denominator);
                    if (fraction.HasRule)
                    {
                        builder.Append(@"\frac{" + numerator + "}{" + denominator + "}");
                    }
                    else
                    {
                        string command = null;
                        if (fraction.LeftDelimiter == null && fraction.RightDelimiter == null)
                        {
                            command = "atop";
                        }
                        else if (fraction.LeftDelimiter == "(" && fraction.RightDelimiter == ")")
                        {
                            command = "choose";
                        }
                        else if (fraction.LeftDelimiter == "{" && fraction.RightDelimiter == "}")
                        {
                            command = "brace";
                        }
                        else if (fraction.LeftDelimiter == "[" && fraction.RightDelimiter == "]")
                        {
                            command = "brack";
                        }
                        else
                        {
                            command = $"atopwithdelims{fraction.LeftDelimiter}{fraction.RightDelimiter}";
                        }
                        builder.Append("{" + numerator + @" \" + command + " " + denominator + "}");
                    }
                }
                break;

                case MathAtomType.Radical: {
                    builder.Append(@"\sqrt");
                    var radical = (IRadical)atom;
                    if (radical.Degree != null)
                    {
                        builder.Append($"[{MathListToString(radical.Degree)}]");
                    }
                    builder.Append("{" + MathListToString(radical.Radicand) + "}");
                    break;
                }

                case MathAtomType.Inner: {
                    var inner = (IMathInner)atom;
                    if (inner.LeftBoundary == null && inner.RightBoundary == null)
                    {
                        builder.Append("{" + MathListToString(inner.InnerList) + "}");
                    }
                    else
                    {
                        if (inner.LeftBoundary == null)
                        {
                            builder.Append(@"\left. ");
                        }
                        else
                        {
                            builder.Append(@"\left" + DelimiterToString(inner.LeftBoundary) + " ");
                        }
                        builder.Append(MathListToString(inner.InnerList));
                        if (inner.RightBoundary == null)
                        {
                            builder.Append(@"\right. ");
                        }
                        else
                        {
                            builder.Append(@"\right" + DelimiterToString(inner.RightBoundary) + " ");
                        }
                    }
                    break;
                }

                case MathAtomType.Table: {
                    var table = (IMathTable)atom;
                    if (table.Environment != null)
                    {
                        builder.Append(@"\begin{" + table.Environment + "}");
                    }
                    for (int i = 0; i < table.NRows; i++)
                    {
                        var row = table.Cells[i];
                        for (int j = 0; j < row.Count; j++)
                        {
                            var cell = row[j];
                            if (table.Environment == "matrix")
                            {
                                if (cell.Count >= 1 && cell[0].AtomType == MathAtomType.Style)
                                {
                                    // remove the first atom.
                                    var atoms = cell.Atoms.GetRange(1, cell.Count - 1);
                                    cell = MathLists.WithAtoms(atoms.ToArray());
                                }
                            }
                            if (table.Environment == "eqalign" || table.Environment == "aligned" || table.Environment == "split")
                            {
                                if (j == 1 && cell.Count >= 1 && cell[0].AtomType == MathAtomType.Ordinary && string.IsNullOrEmpty(cell[0].Nucleus))
                                {
                                    // empty nucleus added for spacing. Remove it.
                                    var atoms = cell.Atoms.GetRange(1, cell.Count - 1);
                                    cell = MathLists.WithAtoms(atoms.ToArray());
                                }
                            }
                            builder.Append(MathListToString(cell));
                            if (j < row.Count - 1)
                            {
                                builder.Append("&");
                            }
                        }
                        if (i < table.NRows - 1)
                        {
                            builder.Append(@"\\ ");
                        }
                    }
                    if (table.Environment != null)
                    {
                        builder.Append(@"\end{" + table.Environment + "}");
                    }
                    break;
                }

                case MathAtomType.Overline: {
                    var over = (IOverline)atom;
                    builder.Append(@"\overline{" + MathListToString(over.InnerList) + "}");
                    break;
                }

                case MathAtomType.Underline: {
                    var under = (IUnderline)atom;
                    builder.Append(@"\underline{" + MathListToString(under.InnerList) + "}");
                    break;
                }

                case MathAtomType.Accent: {
                    var accent = (IAccent)atom;
                    var list   = accent.InnerList;
                    accent.InnerList = null; //for lookup
                    builder.Append(@"\" + MathAtoms.Commands[(MathAtom)atom] + "{" + MathListToString(list) + "}");
                    break;
                }

                case MathAtomType.LargeOperator: {
                    var op               = (LargeOperator)atom;
                    var command          = MathAtoms.LatexSymbolNameForAtom(op);
                    var originalOperator = (LargeOperator)MathAtoms.ForLatexSymbolName(command);
                    builder.Append($@"\{command} ");
                    if (originalOperator.Limits != op.Limits)
                    {
                        switch (op.Limits)
                        {
                        case true:
                            builder.Append(@"\limits ");
                            break;

                        case false:
                            if (!op.NoLimits)
                            {
                                builder.Append(@"\nolimits ");
                            }
                            break;

                        case null:
                            break;
                        }
                    }
                    break;
                }

                case MathAtomType.Space: {
                    var space    = (ISpace)atom;
                    var intSpace = (int)space.Length;
                    if (SpaceToCommands.ContainsKey(intSpace) && intSpace == space.Length)
                    {
                        var command = SpaceToCommands[intSpace];
                        builder.Append(@"\" + command + " ");
                    }
                    else
                    {
                        if (space.IsMu)
                        {
                            builder.Append(@"\mkern" + space.Length.ToString("0.0") + "mu");
                        }
                        else
                        {
                            builder.Append(@"\kern" + space.Length.ToString("0.0") + "pt");
                        }
                    }
                    break;
                }

                case MathAtomType.Style: {
                    var style   = (IStyle)atom;
                    var command = StyleToCommands[style.LineStyle];
                    builder.Append(@"\" + command + " ");
                    break;
                }

                case MathAtomType.Color: {
                    var color = (IColor)atom;
                    builder.Append($@"\color{{{color.ColorString}}}{{{MathListToString(color.InnerList)}}}");
                    break;
                }

                case MathAtomType.Group:
                    builder.AppendInBraces(MathListToString(((Group)atom).InnerList), NullHandling.EmptyContent);
                    break;

                case MathAtomType.Prime:
                    builder.Append('\'', ((Prime)atom).Length);
                    break;

                default: {
                    var aNucleus = atom.Nucleus;
                    if (String.IsNullOrEmpty(aNucleus))
                    {
                        builder.Append(@"{}");
                    }
                    else if (aNucleus == "\u2236")
                    {
                        builder.Append(":");
                    }
                    else if (aNucleus == "\u2212")
                    {
                        builder.Append("-");
                    }
                    else
                    {
                        var command = MathAtoms.LatexSymbolNameForAtom((MathAtom)atom);
                        if (command == null)
                        {
                            if (atom is Extension.I_ExtensionAtom ext)
                            {
                                builder.Append(Extension._MathListDestructor.MathAtomToString(ext));
                            }
                            else
                            {
                                builder.Append(aNucleus);
                            }
                        }
                        else
                        {
                            builder.Append(@"\" + command + " ");
                        }
                    }
                    break;
                }
                }
                if (atom.Subscript != null)
                {
                    var scriptString = MathListToString(atom.Subscript);
                    builder.Append(scriptString.Length == 1 ? $"_{scriptString}" : $"_{{{scriptString}}}");
                }

                if (atom.Superscript != null)
                {
                    var scriptString = MathListToString(atom.Superscript);
                    builder.Append(scriptString.Length == 1 ? $"^{scriptString}" : $"^{{{scriptString}}}");
                }
            }
            if (currentFontStyle != FontStyle.Default)
            {
                builder.Append("}");
            }
            return(builder.ToString());
        }
        internal IMathList BuildInternal(bool oneCharOnly, char stopChar)
        {
            if (oneCharOnly && stopChar > 0)
            {
                throw new InvalidOperationException("Cannot set both oneCharOnly and stopChar");
            }
            var       r        = new MathList();
            IMathAtom prevAtom = null;

            while (HasCharacters)
            {
                if (_error != null)
                {
                    return(null);
                }
                IMathAtom atom;
                var       ch = GetNextCharacter();
                if (oneCharOnly)
                {
                    if (ch == '^' || ch == '}' || ch == '_' || ch == '&')
                    {
                        // this is not the character we are looking for. They are for the caller to look at.
                        UnlookCharacter();
                        return(r);
                    }
                }
                if (stopChar > 0 && ch == stopChar)
                {
                    return(r);
                }
                switch (ch)
                {
                case '^':
                    if (prevAtom == null || prevAtom.Superscript != null || !prevAtom.ScriptsAllowed)
                    {
                        prevAtom = MathAtoms.Create(MathAtomType.Ordinary, string.Empty);
                        r.Add(prevAtom);
                    }
                    // this is a superscript for the previous atom.
                    // note, if the next char is StopChar, it will be consumed and doesn't count as stop.
                    prevAtom.Superscript = this.BuildInternal(true);
                    continue;

                case '_':
                    if (prevAtom == null || prevAtom.Subscript != null || !prevAtom.ScriptsAllowed)
                    {
                        prevAtom = MathAtoms.Create(MathAtomType.Ordinary, string.Empty);
                        r.Add(prevAtom);
                    }
                    // this is a subscript for the previous atom.
                    // note, if the next char is StopChar, it will be consumed and doesn't count as stop.
                    prevAtom.Subscript = this.BuildInternal(true);
                    continue;

                case '{':
                    IMathList sublist;
                    if (_currentEnvironment != null && _currentEnvironment.Name == null)
                    {
                        // \\ or \cr which do not have a corrosponding \end
                        var oldEnv = _currentEnvironment;
                        _currentEnvironment = null;
                        sublist             = BuildInternal(false, '}');
                        _currentEnvironment = oldEnv;
                    }
                    else
                    {
                        sublist = BuildInternal(false, '}');
                    }
                    if (sublist == null)
                    {
                        return(null);
                    }
                    prevAtom = sublist.Atoms.LastOrDefault();
                    if (oneCharOnly)
                    {
                        r.Append(sublist);
                        return(r);
                    }
#warning TODO Example
                    //https://phabricator.wikimedia.org/T99369
                    //https://phab.wmfusercontent.org/file/data/xsimlcnvo42siudvwuzk/PHID-FILE-bdcqexocj5b57tj2oezn/math_rendering.png
                    //dt, \text{d}t, \partial t, \nabla\psi \\ \underline\overline{dy/dx, \text{d}y/\text{d}x, \frac{dy}{dx}, \frac{\text{d}y}{\text{d}x}, \frac{\partial^2}{\partial x_1\partial x_2}y} \\ \prime,
                    atom = new Group {
                        InnerList = sublist
                    };
                    break;

                case '}':
                    if (oneCharOnly || stopChar != 0)
                    {
                        throw new InvalidCodePathException("This should have been handled before.");
                    }
                    SetError("Missing opening brace");
                    return(null);

                case '\\':
                    var command = ReadCommand();
                    var done    = StopCommand(command, r, stopChar);
                    if (done != null)
                    {
                        return(done);
                    }
                    if (_error != null)
                    {
                        return(null);
                    }
                    if (ApplyModifier(command, prevAtom))
                    {
                        continue;
                    }
                    var fontStyleQ = MathAtoms.FontStyle(command);
                    if (fontStyleQ.HasValue)
                    {
                        var fontStyle        = fontStyleQ.Value;
                        var oldSpacesAllowed = _textMode;
                        var oldFontStyle     = _currentFontStyle;
                        _textMode         = (command == "text");
                        _currentFontStyle = fontStyle;
                        var childList = BuildInternal(true);
                        if (childList == null)
                        {
                            return(null);
                        }
                        _currentFontStyle = oldFontStyle;
                        _textMode         = oldSpacesAllowed;
                        prevAtom          = childList.Atoms.LastOrDefault();
                        r.Append(childList);
                        if (oneCharOnly)
                        {
                            return(r);
                        }
                        continue;
                    }
                    atom = AtomForCommand(command, stopChar);
                    if (atom == null)
                    {
                        SetError(_error ?? "Internal error");
                        return(null);
                    }
                    break;

                case '&': { // column separation in tables
                    if (_currentEnvironment != null)
                    {
                        return(r);
                    }
                    var table = BuildTable(null, r, false, stopChar);
                    if (table == null)
                    {
                        return(null);
                    }
                    return(MathLists.WithAtoms(table));
                }

                case '\'': // this case is NOT in iosMath
                    int i = 1;
                    while (ExpectCharacter('\''))
                    {
                        i++;
                    }
                    atom = new Prime(i);
                    break;

                default:
                    if (_textMode && ch == ' ')
                    {
                        atom = MathAtoms.ForLatexSymbolName(" ");
                    }
                    else
                    {
                        atom = MathAtoms.ForCharacter(ch);
                        if (atom == null)
                        {
                            // not a recognized character
                            continue;
                        }
                    }
                    break;
                }
                if (atom == null)
                {
                    throw new InvalidCodePathException("Atom shouldn't be null");
                }
                atom.FontStyle = _currentFontStyle;
                r.Add(atom);
                prevAtom = atom;
                if (oneCharOnly)
                {
                    return(r); // we consumed our character.
                }
            }
            if (stopChar > 0)
            {
                if (stopChar == '}')
                {
                    SetError("Missing closing brace");
                }
                else
                {
                    // we never found our stop character.
                    SetError("Expected character not found: " + stopChar.ToString());
                }
            }
            return(r);
        }
        internal IMathAtom AtomForCommand(string command, char stopChar)
        {
            var atom = MathAtoms.ForLatexSymbolName(command);

            if (atom is Accent accent)
            {
                accent.InnerList = BuildInternal(true);
                return(accent);
            }
            if (atom != null)
            {
                return(atom);
            }
            switch (command)
            {
            case "frac":
                return(new Fraction {
                    Numerator = BuildInternal(true),
                    Denominator = BuildInternal(true)
                });

            case "binom":
                return(new Fraction(false)
                {
                    Numerator = BuildInternal(true),
                    Denominator = BuildInternal(true),
                    LeftDelimiter = "(",
                    RightDelimiter = ")"
                });

            case "sqrt":
                var rad = new Radical();
                if (ExpectCharacter('['))
                {
                    rad.Degree   = BuildInternal(false, ']');
                    rad.Radicand = BuildInternal(true);
                }
                else
                {
                    rad.Radicand = BuildInternal(true);
                }
                return(rad);

            case "left":
                var oldInner = _currentInnerAtom;
                _currentInnerAtom = new Inner {
                    LeftBoundary = _BoundaryAtomForDelimiterType("left")
                };
                if (_currentInnerAtom.LeftBoundary == null)
                {
                    return(null);
                }
                _currentInnerAtom.InnerList = BuildInternal(false, stopChar);
                if (_currentInnerAtom.RightBoundary == null)
                {
                    SetError("Missing \\right");
                    return(null);
                }
                var newInner = _currentInnerAtom;
                _currentInnerAtom = oldInner;
                return(newInner);

            case "overline":
                return(new Overline {
                    InnerList = BuildInternal(true)
                });

            case "underline":
                return(new Underline()
                {
                    InnerList = BuildInternal(true)
                });

            case "begin":
                var env = ReadEnvironment();
                if (env == null)
                {
                    return(null);
                }
                var table = BuildTable(env, null, false, stopChar);
                return(table);

            case "color":
                return(new Color {
                    ColorString = ReadColor(),
                    InnerList = BuildInternal(true)
                });

            case "prime":
                SetError(@"\prime won't be supported as Unicode has no matching character. Use ' instead.");
                return(null);

            default:
                var extResult = Extension._MathListBuilder.AtomForCommand(this, command);
                if (extResult != null)
                {
                    return(extResult);
                }
                SetError("Invalid command \\" + command);
                return(null);
            }
        }