<h1>What is <em>LaTeX</em></h1>
<p>If you already know what <em>LaTeX</em> is, then skip down the the section labeled &quot;<em><strong>sloppy syntax</strong></em>&quot;.</p>
<p>There is a type-setting language known as <a href="https://en.wikipedia.org/wiki/LaTeX" rel="noreferrer"><strong>LaTeX</strong></a>.</p>
<p><a href="https://en.wikipedia.org/wiki/LaTeX" rel="noreferrer"><strong>LaTeX</strong></a> even has its very own Stack Exchange Site located <a href="https://tex.stackexchange.com/">here</a>.</p>
<p>You do not have have to be fluent in <a href="https://math.meta.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference"><strong>MathJax</strong></a> or <a href="https://tex.stackexchange.com/"><strong>LaTeX</strong></a> in order to answer this question.</p>
<p>However, it does help to see three examples of the syntax for <strong>LaTeX</strong>:</p>
<pre class="lang-LaTeX prettyprint-override"><code>\kiwi{a+06}{b+09}{x+11}
\binom{n+1}{2k}
\foobar{100}
\jackfruit{1.1}{2.2}{3.33}{4.444}{5.555}{6}
</code></pre>
<p><strong>LaTeX</strong> has various <em><strong>commands</strong></em> and those <em><strong>commands</strong></em> behave somewhat like the <em><strong>functions</strong></em> we find in other programming languages.</p>
<p><code>\binom{n+1}{2k}</code> is an example of a <strong>LaTeX</strong> command with two parameters passed into it.</p>
<p>So, the <code>binom</code> command (like a function or procedure) accepts two input arguments. The input arguments are <code>n+1</code> and <code>2k</code>.</p>
<hr />
<h1>Sloppy Syntax</h1>
<p>Suppose that you wanted to translate something named <strong>LearnTex</strong> into <strong>LaTeX</strong>.</p>
<p>Our imaginary <strong>LearnTex</strong> is a bit like a pre-processor, or macro, for <strong>LaTeX</strong></p>
<p>The primary goal here is to make the <strong>LaTeX</strong> language easier for new people to learn.</p>
<p><strong>LearnTex</strong> would allow correctly spelled <strong>LaTeX</strong>, so it is backwards compatible.</p>
<p>We can define <strong>wrapping delimiters</strong> as follows:</p>
<blockquote>
<ul>
<li>curly braces <code>{}</code></li>
<li>square brackets <code>[]</code></li>
<li>parentheses  <code>()</code></li>
</ul>
</blockquote>
<hr />
<p>Below are the infix delimiters of interest within the current context:</p>
<blockquote>
<ul>
<li>commas <code>,</code></li>
<li>colons <code>:</code></li>
<li>semi-colons <code>;</code></li>
</ul>
</blockquote>
<hr />
<p>Ideally, any infix delimiter in <strong>LearnTex</strong> would be exchangeable (swappable) for any other infix delimiter.</p>
<p>For example, <code>\binom{n+1, 2k}</code> with a comma and <code>\binom{n+1; 2k}</code> with a semi-colon would eventually compile into the same thing.</p>
<div class="s-table-container">
<table class="s-table">
<thead>
<tr>
<th>Example No</th>
<th>INPUT (<strong>LearnTex</strong> )</th>
<th>OUTPUT (<strong>LaTeX</strong>)</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><code>\dfrac{1}{x}</code></td>
<td><code>\dfrac{1}{x}</code></td>
</tr>
<tr>
<td>2</td>
<td><code>\dfrac{1, x}</code></td>
<td><code>\dfrac{1}{x}</code></td>
</tr>
<tr>
<td>3</td>
<td><code>\dfrac(1, x)</code></td>
<td><code>\dfrac{1}{x}</code></td>
</tr>
<tr>
<td>4</td>
<td><code>\dfrac[1; x]</code></td>
<td><code>\dfrac{1}{x}</code></td>
</tr>
<tr>
<td>5</td>
<td><code>\dfrac(1; x)</code></td>
<td><code>\dfrac{1}{x}</code></td>
</tr>
<tr>
<td>6</td>
<td><code>\dfrac(1)(x)</code></td>
<td><code>\dfrac{1}{x}</code></td>
</tr>
<tr>
<td>7</td>
<td><code>\dfrac[1][x]</code></td>
<td><code>\dfrac{1}{x}</code></td>
</tr>
</tbody>
</table>
</div>
<p>In additional to commas (<code>,</code>) being swappable with semi-colons (<code>;</code>) and colons (<code>:</code>), we need functions to look like they are <a href="https://www.google.com/search?client=firefox-b-1-d&amp;q=what%20is%20computer%20programming%20curry" rel="noreferrer">curried</a>.</p>
<p>For example,</p>
<pre class="lang-LaTeX prettyprint-override"><code>\command{x}{y}{z}
\command{x, y, z}
</code></pre>
<p>I wrote, &quot;<em>look like curry</em>&quot;, because we <strong>not</strong> have to have an actual programming curry implementation, like this:</p>
<blockquote>
<p><code>SUM(1, 2, 3)</code> is implemented as:</p>
<ul>
<li>Function call <code>f1(1)</code> returns function <code>f2</code></li>
<li>Function call <code>f2(2)</code> returns function <code>f3</code></li>
<li>Function call <code>f3(3)</code> returns <code>6</code> which is <code>1+2+3</code>.</li>
</ul>
</blockquote>
<p>Instead of implementing curry at a low-level, our goal is to simply replace some ASCII-encoded strings with some other ASCII-encoded strings.</p>
<p>For example, <code>\cmd{1, 2}</code> becomes <code>\cmd{1}{2}</code></p>
<p>Pretend we don't support all of latex. Our interpreter or parser need only translate one command at a time.</p>
<p>In case you are curious, a regular expression for a command with a single input is:</p>
<pre class="lang-None prettyprint-override"><code>\\[a-zA-Z]+[{(\[][a-zA-Z0-9]+[})\]]
</code></pre>
<p>In the end, a LaTeX compiler or interpreter will be used to either interpret or compile the LaTeX into a human-readable pdf file full of a mixture mathematical equations and English.</p>
<p>For example, <code>\dfrac{n}{d}</code> becomes the fraction <em>n</em> divided by <em>d</em>.</p>
<hr />
<h1>What approach would you recommend for converting <strong>LearnTex</strong> into <strong>LaTeX</strong>?</h1>

<compilers><parsing><interpreters><lexing>
How would I allow a variety of different syntaxes for passing arguments into MathJax or LaTeX commands?
CC BY-SA 4.0
2023-05-24T04:46:50.267Z
That regex doesn't look correct - at least not when `\makeatletter`.
RubenVerg
CC BY-SA 4.0
2023-05-24T10:49:17.470Z
@RydwolfPrograms see https://languagedesign.meta.stackexchange.com/q/249/
чистов_n
CC BY-SA 4.0
2023-07-08T00:42:54.793Z
@RubenVerg: I would take the regex as prescribing syntax of LearnTeX rather than describing syntax of TeX.
user570286
CC BY-SA 4.0
2023-07-10T18:09:20.900Z
I’m voting to close this question because it's requesting implementation advice for a very specific problem, not particularly transferrable to other contexts on the site. I think it would be better suited on StackOverflow (although read their [How To Ask](https://stackoverflow.com/help/asking) guide first)
pxeger
<p>I would</p>
<ol>
<li>parse the LearnTeX syntax using a parsing library, such as <a href="https://en.wikipedia.org/wiki/Rust_(programming_language)" rel="nofollow noreferrer">Rust</a>'s <a href="https://lib.rs/crates/combine" rel="nofollow noreferrer"><code>combine</code></a> or <a href="https://en.wikipedia.org/wiki/Haskell" rel="nofollow noreferrer">Haskell</a>'s <a href="https://hackage.haskell.org/package/parsec" rel="nofollow noreferrer"><code>parsec</code></a>, or a parser generator, such as <a href="https://en.wikipedia.org/wiki/OCaml" rel="nofollow noreferrer">OCaml</a>'s <a href="https://pauillac.inria.fr/%7Efpottier/menhir/" rel="nofollow noreferrer"><code>menhir</code></a> or Rust's <a href="https://lib.rs/crates/lalrpop" rel="nofollow noreferrer"><code>lalrpop</code></a>, and then</li>
<li>generate the equivalent TeX syntax (LaTeX is essentially a standard library for TeX, not its own language).  This does not necessarily mean parsing all the input into an intermediate representation that would live in memory in its entirety; the parser could <em>stream</em> information to the generator.  For that, in Rust, one could use an <a href="https://doc.rust-lang.org/std/iter/index.html" rel="nofollow noreferrer">iterator</a>, whereas, in Haskell, I guess the language's lazy evaluation would make the program naturally do this.</li>
</ol>
<p>However, for demonstration, <strong>here's a very manual implementation of a very direct, imperative translator of LearnTeX into TeX</strong>, written in Rust but in a style intended to be accessible to programmers who are familiar with C-family languages other than Rust.  This implementation is tested at the end.  It and its tests can be <strong><a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=b105acd26845d9f38b7499827a526794" rel="nofollow noreferrer">run in the Rust Playground</a></strong> (click the big red &quot;TEST&quot; button in the top left corner).</p>
<pre class="lang-rust prettyprint-override"><code>/// Translates LearnTeX, a syntactic variant of TeX, into TeX
///
/// See &lt;https://langdev.stackexchange.com/questions/1045&gt;.
///
/// This instead, of course, could be written in a functional style;
/// or as a lazy [iterator], which *might* be more efficient when
/// optimized by the compiler; but it is written in an imperative
/// style for clarity to most programmers.
///
/// It could be useful to extend this LearnTeX syntax to allow
/// escaping the argument separators by wrapping them in braces, so
/// that `\command{{a, b, c}}` is translated literally as
/// `\command{{a, b, c}}`, which is largely equivalent to `\command{a,
/// b, c}` for the commands that MathJax supports.
///
/// [iterator]: &lt;https://doc.rust-lang.org/std/iter/index.html&gt;
pub fn learntex_to_tex(input: &amp;str) -&gt; String {
    // `with_capacity` is merely an optimization: we guess that the
    // output will be about as long as the input, so we allocate an
    // output buffer as long as the input, to reduce the number of
    // times that `output`'s backing buffer will need to be grown (and
    // thus reallocated) as we append content from the input to it.
    let mut output = String::with_capacity(input.len());

    // Iterate over the characters in the input.  It would be much
    // more idiomatic and more efficient Rust to use the
    // `input.chars()` iterator to iterate over the characters
    // directly, but instead we iterate by character index, for
    // clarity to non-Rust programmers.
    let mut chars = input.chars();

    // While there is still `Some` character left, iterate over the
    // characters.  When there are no characters left, the `chars`
    // iterator's `next()` method will return `None` and the loop will
    // stop.
    'outer_loop: while let Some(c) = chars.next() {
        // Switch on what the character is here.
        match c {
            '\\' =&gt; {
                // We have encountered a command.  Append a backslash
                // to the output.
                output.push('\\');

                // Command names consist of, and only of, alphabetic
                // characters.  Loop, appending these to the output,
                // until we encounter a non-alphabetic character,
                // which is either the opening delimiter of the
                // command's arguments or (if the command has no
                // arguments) something that is not an opening
                // delimiter.
                let mut char_after_command_name = None;
                while let Some(c) = chars.next() {
                    if c.is_alphabetic() {
                        output.push(c);
                    } else {
                        char_after_command_name = Some(c);
                        break;
                    }
                }

                let opening_delimiter;
                match char_after_command_name {
                    Some(c @ ('{' | '[' | '(')) =&gt; {
                        // LearnTeX allows any of `{...}`, `[...]`,
                        // `(...)` to stand for TeX's `{...}`.  The
                        // `@` means to capture the opening delimiter
                        // as a variable named `c`.  Now we set the
                        // enclosing scope's `opening_delimiter`
                        // variable to that.
                        opening_delimiter = c;
                    }

                    Some(other_char) =&gt; {
                        // This command has ended.  Append the
                        // following character to the output and
                        // continue the outer loop.
                        output.push(other_char);
                        continue 'outer_loop;
                    }

                    None =&gt; {
                        // The input has ended.  End the outer loop.
                        break 'outer_loop;
                    }
                }

                let mut skip_space = false;

                // Loop, parsing the command's arguments.
                'args_loop: loop {
                    // We are in a command's argument.  Output the TeX
                    // opening delimiter for an argument.  LearnTeX
                    // supports only those TeX commands that use
                    // `{...}`-delimited arguments only, no
                    // `[...]`-delimited optional arguments or fancy
                    // `xparse` arguments.
                    output.push('{');

                    // Decide which delimiter will be needed to close
                    // this argument.
                    let closing_delimiter = match opening_delimiter {
                        '{' =&gt; '}',
                        '[' =&gt; ']',
                        '(' =&gt; ')',
                        _ =&gt; unreachable!(),
                    };

                    loop {
                        match chars.next() {
                            Some(',' | ';' | ':') =&gt; {
                                // Any of these three characters is
                                // allowed as an argument separator,
                                // so this argument ends here and a
                                // new one begins.  Output the TeX
                                // closing delimiter for an argument.
                                output.push('}');
                                // The argument separator may be
                                // followed by any amount of
                                // whitespace, which is ignored.
                                skip_space = true;
                                // Continue to the next argument.
                                continue 'args_loop;
                            }

                            Some(c) if c == closing_delimiter =&gt; {
                                // We have encountered the end of the
                                // argument list.  Output the TeX
                                // closing delimiter for an argument.
                                output.push('}');
                                break 'args_loop;
                            }

                            Some(c) if skip_space &amp;&amp; c.is_whitespace() =&gt; {
                                // Ignore the whitespace.
                            }

                            Some(other_char) =&gt; {
                                // This character is part of an
                                // argument.  Output it.
                                output.push(other_char);
                                // Also, we encountered a non-space
                                // character, so stop skipping spaces.
                                skip_space = false;
                            }

                            None =&gt; {
                                // The argument was not closed.  In
                                // practice, there are much more
                                // graceful and helpful ways of
                                // handling such an error than
                                // throwing this exception.
                                panic!(&quot;unclosed argument&quot;);
                            }
                        }
                    }
                }
            }

            other_char =&gt; {
                // We encountered some character that is not part of a
                // command.  Append the character to the output.
                output.push(other_char);
            }
        }
    }

    // Return the output.
    output
}

#[test]
fn test() {
    assert_eq!(learntex_to_tex(r&quot;abc&quot;), r&quot;abc&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc&quot;), r&quot;\abc&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{}&quot;), r&quot;\abc{}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{x}&quot;), r&quot;\abc{x}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc[x]&quot;), r&quot;\abc{x}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x)&quot;), r&quot;\abc{x}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{x, y}&quot;), r&quot;\abc{x}{y}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc[x, y]&quot;), r&quot;\abc{x}{y}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x, y)&quot;), r&quot;\abc{x}{y}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{x, y, z}&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc[x, y, z]&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x, y, z)&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{x; y}&quot;), r&quot;\abc{x}{y}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc[x; y]&quot;), r&quot;\abc{x}{y}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x; y)&quot;), r&quot;\abc{x}{y}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{x; y; z}&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc[x; y; z]&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x; y; z)&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{x: y}&quot;), r&quot;\abc{x}{y}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc[x: y]&quot;), r&quot;\abc{x}{y}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x: y)&quot;), r&quot;\abc{x}{y}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{x: y: z}&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc[x: y: z]&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x: y: z)&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{x, y; z}&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc[x; y, z]&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x, y: z)&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x: y, z)&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x; y: z)&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc(x: y; z)&quot;), r&quot;\abc{x}{y}{z}&quot;);
    assert_eq!(learntex_to_tex(r&quot;\abc{x} def \ghi[y]&quot;), r&quot;\abc{x} def \ghi{y}&quot;);
}
</code></pre>
<p>If you are interested in seeing this done with <code>combine</code>, I can write an example of that.</p>
<p>Edit: My manual implementation is wrong in that it does not translate commands with 'curried' arguments, i.e., it translates <code>\abc(x)[y]</code> as <code>\abc{x}[y]</code>, seeing the <code>[y]</code> as not an argument to the command. I'm trying to fix this.</p>

CC BY-SA 4.0
2023-07-07T21:18:08.723Z
2023-07-07T22:08:23.250Z
2023-07-07T22:08:23.250Z
user570286
# How would I allow a variety of different syntaxes for passing arguments into MathJax or LaTeX commands?
Posted by: username_0 on 2023-05-24
What is LaTeX
If you already know what LaTeX is, then skip down the the section labeled "sloppy syntax".
There is a type-setting language known as LaTeX.
LaTeX even has its very own Stack Exchange Site located here.
You do not have have to be fluent in MathJax or LaTeX in order to answer this question.
However, it does help to see three examples of the syntax for LaTeX:
\kiwi{a+06}{b+09}{x+11}
\binom{n+1}{2k}
\foobar{100}
\jackfruit{1.1}{2.2}{3.33}{4.444}{5.555}{6}

LaTeX has various commands and those commands behave somewhat like the functions we find in other programming languages.
\binom{n+1}{2k} is an example of a LaTeX command with two parameters passed into it.
So, the binom command (like a function or procedure) accepts two input arguments. The input arguments are n+1 and 2k.

Sloppy Syntax
Suppose that you wanted to translate something named LearnTex into LaTeX.
Our imaginary LearnTex is a bit like a pre-processor, or macro, for LaTeX
The primary goal here is to make the LaTeX language easier for new people to learn.
LearnTex would allow correctly spelled LaTeX, so it is backwards compatible.
We can define wrapping delimiters as follows:


curly braces {}
square brackets []
parentheses  ()



Below are the infix delimiters of interest within the current context:


commas ,
colons :
semi-colons ;



Ideally, any infix delimiter in LearnTex would be exchangeable (swappable) for any other infix delimiter.
For example, \binom{n+1, 2k} with a comma and \binom{n+1; 2k} with a semi-colon would eventually compile into the same thing.




Example No
INPUT (LearnTex )
OUTPUT (LaTeX)




1
\dfrac{1}{x}
\dfrac{1}{x}


2
\dfrac{1, x}
\dfrac{1}{x}


3
\dfrac(1, x)
\dfrac{1}{x}


4
\dfrac[1; x]
\dfrac{1}{x}


5
\dfrac(1; x)
\dfrac{1}{x}


6
\dfrac(1)(x)
\dfrac{1}{x}


7
\dfrac[1][x]
\dfrac{1}{x}




In additional to commas (,) being swappable with semi-colons (;) and colons (:), we need functions to look like they are curried.
For example,
\command{x}{y}{z}
\command{x, y, z}

I wrote, "look like curry", because we not have to have an actual programming curry implementation, like this:

SUM(1, 2, 3) is implemented as:

Function call f1(1) returns function f2
Function call f2(2) returns function f3
Function call f3(3) returns 6 which is 1+2+3.


Instead of implementing curry at a low-level, our goal is to simply replace some ASCII-encoded strings with some other ASCII-encoded strings.
For example, \cmd{1, 2} becomes \cmd{1}{2}
Pretend we don't support all of latex. Our interpreter or parser need only translate one command at a time.
In case you are curious, a regular expression for a command with a single input is:
\\[a-zA-Z]+[{(\[][a-zA-Z0-9]+[})\]]

In the end, a LaTeX compiler or interpreter will be used to either interpret or compile the LaTeX into a human-readable pdf file full of a mixture mathematical equations and English.
For example, \dfrac{n}{d} becomes the fraction n divided by d.

What approach would you recommend for converting LearnTex into LaTeX?
**Tags:** compilers parsing interpreters lexing
> **Comments:**
> **username_1**: That regex doesn't look correct - at least not when `\makeatletter`.  
> **username_2**: @RydwolfPrograms see https://languagedesign.meta.stackexchange.com/q/249/  
> **username_3**: @RubenVerg: I would take the regex as prescribing syntax of LearnTeX rather than describing syntax of TeX.  
> **username_4**: I’m voting to close this question because it's requesting implementation advice for a very specific problem, not particularly transferrable to other contexts on the site. I think it would be better suited on StackOverflow (although read their [How To Ask](https://stackoverflow.com/help/asking) guide first)  
---
## 1 answers
### Answer by username_3 on 2023-07-07. Score: 1
I would

parse the LearnTeX syntax using a parsing library, such as Rust's combine or Haskell's parsec, or a parser generator, such as OCaml's menhir or Rust's lalrpop, and then
generate the equivalent TeX syntax (LaTeX is essentially a standard library for TeX, not its own language).  This does not necessarily mean parsing all the input into an intermediate representation that would live in memory in its entirety; the parser could stream information to the generator.  For that, in Rust, one could use an iterator, whereas, in Haskell, I guess the language's lazy evaluation would make the program naturally do this.

However, for demonstration, here's a very manual implementation of a very direct, imperative translator of LearnTeX into TeX, written in Rust but in a style intended to be accessible to programmers who are familiar with C-family languages other than Rust.  This implementation is tested at the end.  It and its tests can be run in the Rust Playground (click the big red "TEST" button in the top left corner).
/// Translates LearnTeX, a syntactic variant of TeX, into TeX
///
/// See .
///
/// This instead, of course, could be written in a functional style;
/// or as a lazy [iterator], which *might* be more efficient when
/// optimized by the compiler; but it is written in an imperative
/// style for clarity to most programmers.
///
/// It could be useful to extend this LearnTeX syntax to allow
/// escaping the argument separators by wrapping them in braces, so
/// that `\command{{a, b, c}}` is translated literally as
/// `\command{{a, b, c}}`, which is largely equivalent to `\command{a,
/// b, c}` for the commands that MathJax supports.
///
/// [iterator]: 
pub fn learntex_to_tex(input: &str) -> String {
    // `with_capacity` is merely an optimization: we guess that the
    // output will be about as long as the input, so we allocate an
    // output buffer as long as the input, to reduce the number of
    // times that `output`'s backing buffer will need to be grown (and
    // thus reallocated) as we append content from the input to it.
    let mut output = String::with_capacity(input.len());

    // Iterate over the characters in the input.  It would be much
    // more idiomatic and more efficient Rust to use the
    // `input.chars()` iterator to iterate over the characters
    // directly, but instead we iterate by character index, for
    // clarity to non-Rust programmers.
    let mut chars = input.chars();

    // While there is still `Some` character left, iterate over the
    // characters.  When there are no characters left, the `chars`
    // iterator's `next()` method will return `None` and the loop will
    // stop.
    'outer_loop: while let Some(c) = chars.next() {
        // Switch on what the character is here.
        match c {
            '\\' => {
                // We have encountered a command.  Append a backslash
                // to the output.
                output.push('\\');

                // Command names consist of, and only of, alphabetic
                // characters.  Loop, appending these to the output,
                // until we encounter a non-alphabetic character,
                // which is either the opening delimiter of the
                // command's arguments or (if the command has no
                // arguments) something that is not an opening
                // delimiter.
                let mut char_after_command_name = None;
                while let Some(c) = chars.next() {
                    if c.is_alphabetic() {
                        output.push(c);
                    } else {
                        char_after_command_name = Some(c);
                        break;
                    }
                }

                let opening_delimiter;
                match char_after_command_name {
                    Some(c @ ('{' | '[' | '(')) => {
                        // LearnTeX allows any of `{...}`, `[...]`,
                        // `(...)` to stand for TeX's `{...}`.  The
                        // `@` means to capture the opening delimiter
                        // as a variable named `c`.  Now we set the
                        // enclosing scope's `opening_delimiter`
                        // variable to that.
                        opening_delimiter = c;
                    }

                    Some(other_char) => {
                        // This command has ended.  Append the
                        // following character to the output and
                        // continue the outer loop.
                        output.push(other_char);
                        continue 'outer_loop;
                    }

                    None => {
                        // The input has ended.  End the outer loop.
                        break 'outer_loop;
                    }
                }

                let mut skip_space = false;

                // Loop, parsing the command's arguments.
                'args_loop: loop {
                    // We are in a command's argument.  Output the TeX
                    // opening delimiter for an argument.  LearnTeX
                    // supports only those TeX commands that use
                    // `{...}`-delimited arguments only, no
                    // `[...]`-delimited optional arguments or fancy
                    // `xparse` arguments.
                    output.push('{');

                    // Decide which delimiter will be needed to close
                    // this argument.
                    let closing_delimiter = match opening_delimiter {
                        '{' => '}',
                        '[' => ']',
                        '(' => ')',
                        _ => unreachable!(),
                    };

                    loop {
                        match chars.next() {
                            Some(',' | ';' | ':') => {
                                // Any of these three characters is
                                // allowed as an argument separator,
                                // so this argument ends here and a
                                // new one begins.  Output the TeX
                                // closing delimiter for an argument.
                                output.push('}');
                                // The argument separator may be
                                // followed by any amount of
                                // whitespace, which is ignored.
                                skip_space = true;
                                // Continue to the next argument.
                                continue 'args_loop;
                            }

                            Some(c) if c == closing_delimiter => {
                                // We have encountered the end of the
                                // argument list.  Output the TeX
                                // closing delimiter for an argument.
                                output.push('}');
                                break 'args_loop;
                            }

                            Some(c) if skip_space && c.is_whitespace() => {
                                // Ignore the whitespace.
                            }

                            Some(other_char) => {
                                // This character is part of an
                                // argument.  Output it.
                                output.push(other_char);
                                // Also, we encountered a non-space
                                // character, so stop skipping spaces.
                                skip_space = false;
                            }

                            None => {
                                // The argument was not closed.  In
                                // practice, there are much more
                                // graceful and helpful ways of
                                // handling such an error than
                                // throwing this exception.
                                panic!("unclosed argument");
                            }
                        }
                    }
                }
            }

            other_char => {
                // We encountered some character that is not part of a
                // command.  Append the character to the output.
                output.push(other_char);
            }
        }
    }

    // Return the output.
    output
}

#[test]
fn test() {
    assert_eq!(learntex_to_tex(r"abc"), r"abc");
    assert_eq!(learntex_to_tex(r"\abc"), r"\abc");
    assert_eq!(learntex_to_tex(r"\abc{}"), r"\abc{}");
    assert_eq!(learntex_to_tex(r"\abc{x}"), r"\abc{x}");
    assert_eq!(learntex_to_tex(r"\abc[x]"), r"\abc{x}");
    assert_eq!(learntex_to_tex(r"\abc(x)"), r"\abc{x}");
    assert_eq!(learntex_to_tex(r"\abc{x, y}"), r"\abc{x}{y}");
    assert_eq!(learntex_to_tex(r"\abc[x, y]"), r"\abc{x}{y}");
    assert_eq!(learntex_to_tex(r"\abc(x, y)"), r"\abc{x}{y}");
    assert_eq!(learntex_to_tex(r"\abc{x, y, z}"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc[x, y, z]"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc(x, y, z)"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc{x; y}"), r"\abc{x}{y}");
    assert_eq!(learntex_to_tex(r"\abc[x; y]"), r"\abc{x}{y}");
    assert_eq!(learntex_to_tex(r"\abc(x; y)"), r"\abc{x}{y}");
    assert_eq!(learntex_to_tex(r"\abc{x; y; z}"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc[x; y; z]"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc(x; y; z)"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc{x: y}"), r"\abc{x}{y}");
    assert_eq!(learntex_to_tex(r"\abc[x: y]"), r"\abc{x}{y}");
    assert_eq!(learntex_to_tex(r"\abc(x: y)"), r"\abc{x}{y}");
    assert_eq!(learntex_to_tex(r"\abc{x: y: z}"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc[x: y: z]"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc(x: y: z)"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc{x, y; z}"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc[x; y, z]"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc(x, y: z)"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc(x: y, z)"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc(x; y: z)"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc(x: y; z)"), r"\abc{x}{y}{z}");
    assert_eq!(learntex_to_tex(r"\abc{x} def \ghi[y]"), r"\abc{x} def \ghi{y}");
}

If you are interested in seeing this done with combine, I can write an example of that.
Edit: My manual implementation is wrong in that it does not translate commands with 'curried' arguments, i.e., it translates \abc(x)[y] as \abc{x}[y], seeing the [y] as not an argument to the command. I'm trying to fix this.
---
