private MathList?BuildInternal(bool oneCharOnly, char stopChar = '\0', MathList?r = null) { if (oneCharOnly && stopChar > '\0') { throw new InvalidCodePathException("Cannot set both oneCharOnly and stopChar"); } r ??= new MathList(); MathAtom?prevAtom = null; while (HasCharacters) { if (Error != null) { return(null); } MathAtom atom; switch (GetNextCharacter()) { case var ch when oneCharOnly && (ch == '^' || ch == '}' || ch == '_' || ch == '&'): SetError($"{ch} cannot appear as an argument to a command"); return(r); case var ch when stopChar > '\0' && ch == stopChar: return(r); case '^': if (prevAtom == null || prevAtom.Superscript.IsNonEmpty() || !prevAtom.ScriptsAllowed) { prevAtom = new 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. this.BuildInternal(true, r: prevAtom.Superscript); if (Error != null) { return(null); } continue; case '_': if (prevAtom == null || prevAtom.Subscript.IsNonEmpty() || !prevAtom.ScriptsAllowed) { prevAtom = new 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. this.BuildInternal(true, r: prevAtom.Subscript); if (Error != null) { return(null); } continue; case '{': MathList?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(); r.Append(sublist); if (oneCharOnly) { return(r); } continue; #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, case '}' when oneCharOnly || stopChar != 0: throw new InvalidCodePathException("This should have been handled before."); case '}': 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; } if (LaTeXSettings.FontStyles.TryGetValue(command, out var fontStyle)) { 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; } switch (AtomForCommand(command, stopChar)) { case null: SetError(Error ?? "Internal error"); return(null); case var a: atom = a; break; } break; case '&': // column separation in tables if (_currentEnvironment != null) { return(r); } var table = BuildTable(null, r, false, stopChar); if (table == null) { return(null); } return(new MathList(table)); case '\'': // this case is NOT in iosMath int i = 1; while (ExpectCharacter('\'')) { i++; } atom = new Prime(i); break; case ' ' when _textMode: atom = new Ordinary(" "); break; case var ch when ch <= sbyte.MaxValue: if (LaTeXSettings.ForAscii((sbyte)ch) is MathAtom asciiAtom) { atom = asciiAtom; } else { continue; // Ignore ASCII spaces and control characters } break; case var ch: // not a recognized character, display it directly atom = new Ordinary(ch.ToStringInvariant()); break; } 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.ToStringInvariant()); } } return(r); }
private MathAtom?AtomForCommand(string command, char stopChar) { switch (LaTeXSettings.AtomForCommand(command)) { case Accent accent: var innerList = BuildInternal(true); if (innerList is null) { return(null); } return(new Accent(accent.Nucleus, innerList)); case MathAtom atom: return(atom); } switch (command) { case "frac": var numerator = BuildInternal(true); if (numerator is null) { return(null); } var denominator = BuildInternal(true); if (denominator is null) { return(null); } return(new Fraction(numerator, denominator)); case "binom": numerator = BuildInternal(true); if (numerator is null) { return(null); } denominator = BuildInternal(true); if (denominator is null) { return(null); } return(new Fraction(numerator, denominator, false) { LeftDelimiter = "(", RightDelimiter = ")" }); case "sqrt": var degree = ExpectCharacter('[') ? BuildInternal(false, ']') : new MathList(); if (degree is null) { return(null); } var radicand = BuildInternal(true); return(radicand != null ? new Radical(degree, radicand) : null); case "left": var oldInner = _currentInner; var leftBoundary = BoundaryAtomForDelimiterType("left"); if (!(leftBoundary is Boundary left)) { return(null); } _currentInner = new InnerEnvironment(); var innerList = BuildInternal(false, stopChar); if (innerList is null) { return(null); } if (!(_currentInner.RightBoundary is Boundary right)) { SetError("Missing \\right"); return(null); } _currentInner = oldInner; return(new Inner(left, innerList, right)); case "overline": innerList = BuildInternal(true); if (innerList is null) { return(null); } return(new Overline(innerList)); case "underline": innerList = BuildInternal(true); if (innerList is null) { return(null); } return(new Underline(innerList)); case "begin": var env = ReadEnvironment(); if (env == null) { return(null); } return(BuildTable(env, null, false, stopChar)); case "color": return((ReadColor()) switch { { } color when BuildInternal(true) is { } ml => new Color(color, ml), _ => null, });