TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Out of curiosity, I wonder how would a macro coded in expl3 that does the basics of mhchem (as in section basics in the mhchem manual) looks like?


I have found examples in TeX and LuaTeX in http://wiki.contextgarden.net/Programming_in_LuaTeX but their input style is too close to math (in my view, not as convenient asmhchem).

So, gurus, please showcase the power of expl3.

share|improve this question
I wrote the wiki page that you cite. I will start by noting that i know nothin about typesetring chemical formulas but what is so so ugly about the input syntax. I glanced at mhchem syntax and the main difference appears to how underscores are handled. If you have a formal grammer of how mhchem parses its input it should be straigjtforward to write the parse in lpeg. – Aditya Sep 6 '10 at 23:15
mhchem.sty is 1300 lines long. What exactly are you asking to be reimplemented? – Will Robertson Sep 7 '10 at 0:39
And what would a reimplementation get us? P.S. @Aditya: "without exploding your head" is a great phrase :) I haven't used LPEG but that seems like a good approach. – Will Robertson Sep 7 '10 at 4:32
@Aditya: the syntax is too close to math, isn't it? @Will: I don't know if implementing the basics of mhchem is a reimplementation of mhchem. It is 1300 lines and not easy to read. I was curious about whether using expl3 will make the code easier to read just like what LPEG from Lua does. – Leo Liu Sep 7 '10 at 6:30
I like the question. I don’t know expl3 at all but if it can simplify parsing TeX, then a simple grammar such as the above might be well suited to showcase it. However, @Leo, providing a simple formal grammar might help make the question clearer and encourage answers. – Konrad Rudolph Sep 7 '10 at 7:04
up vote 6 down vote accepted

I wouldn't have a clue if this is sufficient to fully implement what \ce is capable of, but here's a quick example of how I'd go about this. Note that there are no state machines or active characters, just judicious use of search'n'replace. The TeX equivalent, I suppose, of a regexp solution. Who knows if it's efficient, though.

  fixltx2e, % for \textsubscript
  amstext   % for \text



% \catcode 12 strings of otherwise special chars:
\tl_set:Nx \c_underscore_char { \cs_to_str:N \_ }
\tl_set:Nx \c_caret_char      { \cs_to_str:N \^ }
\tl_set:Nx \c_bgroup_char     { \cs_to_str:N \{ }
\tl_set:Nx \c_egroup_char     { \cs_to_str:N \} }

\DeclareDocumentCommand \ce {m} {
  \tl_set:Nx \l_ce_tl { \tl_to_str:n {#1} }

  \tl_replace_all_in:Nnn \l_ce_tl {1} {\ce_sub:w{1}}
  \tl_replace_all_in:Nnn \l_ce_tl {2} {\ce_sub:w{2}}
  \tl_replace_all_in:Nnn \l_ce_tl {3} {\ce_sub:w{3}}
  \tl_replace_all_in:Nnn \l_ce_tl {4} {\ce_sub:w{4}}
  \tl_replace_all_in:Nnn \l_ce_tl {5} {\ce_sub:w{5}}
  \tl_replace_all_in:Nnn \l_ce_tl {6} {\ce_sub:w{6}}
  \tl_replace_all_in:Nnn \l_ce_tl {7} {\ce_sub:w{7}}
  \tl_replace_all_in:Nnn \l_ce_tl {8} {\ce_sub:w{8}}
  \tl_replace_all_in:Nnn \l_ce_tl {9} {\ce_sub:w{9}}
  \tl_replace_all_in:Nnn \l_ce_tl {0} {\ce_sub:w{0}}

  \tl_replace_all_in:Non \l_ce_tl \c_caret_char {\ce_super:w}
  \tl_replace_all_in:Non \l_ce_tl \c_underscore_char {\ce_sub:w}

  \tl_replace_all_in:Nnn \l_ce_tl {+} {\ce_super:w{+}}
  \tl_replace_all_in:Nnn \l_ce_tl {-} {\ce_super:w{$-$}}

  % fix syntax like "^2":
  \tl_replace_all_in:Nnn \l_ce_tl {\ce_super:w \ce_sub:w} {\ce_super:w}

  % finally, fix up braces that were detokenized:
  \tl_rescan:nV {\ExplSyntaxOn} \l_ce_tl

\cs_set:Npn \ce_super:w #1 {
  \peek_meaning_remove_ignore_spaces:NTF \ce_sub:w
  {\textsupersubscript{\ce_disable_subsuper: #1}}
  {\textsuperscript{\ce_disable_subsuper: #1}}

\cs_set:Npn \ce_sub:w #1 {
  \peek_meaning_remove_ignore_spaces:NTF \ce_super:w
  {\textsubsuperscript{\ce_disable_subsuper: #1}}
  {\textsubscript{\ce_disable_subsuper: #1}}

\cs_set:Nn \ce_disable_subsuper: {
  \cs_set_eq:NN \ce_super:w \use:n
  \cs_set_eq:NN \ce_sub:w \use:n

% Need these variants:
\cs_generate_variant:Nn \tl_replace_all_in:Nnn {Non}
\cs_generate_variant:Nn \tl_rescan:nn {nV}



The premise of \ce is that the argument is turned into a string before replacing the various characters with macros to expand to the desired output. First the numbers are replaced by themselves inside a conditional subscript, then ^ and _ are replaced macros to perform "conditional superscripts and subscripts" (resp.), and finally + and - are replaced by themselves superscripted.

In case of syntax like ^2, which would be replaced by [superscript]+[subscript 2], we replace the combination of [superscript]+[subscript] with the conditional superscript alone.

Finally, the string is the "retokenised" using the expl3 wrapper around \scantokens to perform all of the typesetting.

The "conditional subscripts" and "conditional superscripts" that I refer to above are simply text sub-/super-scripts that deactivates themselves for nested material. E.g., [subscript [subscript x]] is interpreted as [subscript x]. This allows things like + inside a superscript not superscripting itself again.

share|improve this answer
If you read through mhchem then I don't think efficiency comes into it. The approach above would leave a few questions, for example how to deal with single bonds (\ce{C-H}) and also escape to math mode (\ce{$i\text{-Pr}$-O}). However, it'd a good place to start! – Joseph Wright Sep 29 '10 at 17:44
Although I do not understand how this works, the expl3 code is significantly more readable than the traditional TeX code. Great. – Aditya Sep 29 '10 at 19:43
@Joseph I was literally just coding to the tests shown :) I'm sure it can be improved. @Aditya some explanation to the code added at the bottom. – Will Robertson Sep 30 '10 at 0:58
thanks for the example. Sorry I am late to tag this as the answer. – Leo Liu May 21 '11 at 7:58

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.