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.
\documentclass{article}
\usepackage{expl3,xparse,
fixltx2e, % for \textsubscript
amstext % for \text
}
\newcommand\textsubsuperscript[2]{$_{\text{#1}}^{\text{#2}}$}
\newcommand\textsupersubscript[2]{$_{\text{#2}}^{\text{#1}}$}
\ExplSyntaxOn
% \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}
\ExplSyntaxOff
\begin{document}
\ce{Y^{99}+}\par
\ce{Y^{99+}}\par
\ce{H20}\par
\ce{Sb2O3}\par
\ce{H+}\par
\ce{CrO4^2-}\par
\ce{AgCl2-}\par
\ce{[AgCl2]-}\par
\ce{H2_{(aq)}}\par
\ce{NO3-}\par
\ce{(NH4)2S}\par
\end{document}
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.
:)I haven't used LPEG but that seems like a good approach. – Will Robertson Sep 7 '10 at 4:32expl3will make the code easier to read just like what LPEG from Lua does. – Leo Liu Sep 7 '10 at 6:30expl3at 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