I'm trying to make a command that goes in \everymath which formats certain specified characters (automatically puts v in \mathbf, etc.). That part I've actually already figured out, but what's giving me problems is the processing itself. It seems to be separating commands within math mode, either displaying incorrectly or just completely breaking.
This is a minimal version of it that just steps through and changes nothing (in theory):
\ExplSyntaxOn
\NewDocumentCommand{\mformat}{+m}{%
\exp_after:wN #1 \mode_if_math:TF { \mformat } { }
}
\ExplSyntaxOff
It's used like so:
\everymath{\mformat}
Here's a few examples of how it breaks:
\(\vec{v}\)
\(\mathbf{v}\) % Just gives "Missing } inserted." error
Any ideas on how to fix this?
Edit: Something new I noticed; if I suround the problem commands with braces (like with mathbf, \({\mathbf{v}}\) works fine), they suddenly work perfectly. No idea why this happens.
I've marked egreg's answer as the solution, since it answered the question I asked, but for posterity here is the solution to the question I should have asked (based on PhelypeOleinik's comments)
\ExplSyntaxOn
% Used to keep track of already active characters
\tl_new:N \g__mformat_mathactive_tl
% Used to figure out which characters need to be made normal again
\tl_new:N \l__mformat_remove_mathactive_tl
% Used to keep track of added characters from this iteration
\tl_new:N \l__mformat_used_tl
% Using https://tex.stackexchange.com/a/611898/261875
% and https://tex.stackexchange.com/a/299805/261875
\NewDocumentCommand{\mformat}{m}{
% By default remove all previous active characters
\tl_set_eq:NN \l__mformat_remove_mathactive_tl \g__mformat_mathactive_tl
\tl_set:Nn \l__mformat_used_tl {}
\tl_if_empty:nTF { #1 } {} {
% Parse the formatting
\cs_set:Npn __mformat_parse:w ##1[##2]##3\relax {
% Process each character in the set
\tl_map_inline:nn { ##2 } {
\tl_if_in:NnTF \g__mformat_mathactive_tl { ####1 } {
% If this character is already active, keep it active
\tl_remove_once:Nn \l__mformat_remove_mathactive_tl { ####1 }
% Check if the character has been used this iteration
\tl_if_in:NnTF \l__mformat_used_tl {####1} {
% Helper needed to have something expandable once
\cs_set_eq:Nc \__mformat_letter_helper:
{ __mformat_letter_new_####1: }
% Add a formatting option to the letter
\cs_set:cx { __mformat_letter_new_####1: } {
\exp_not:N ##1 { \exp_not:o \__mformat_letter_helper: }
}
} {
% Record that this has been used
\tl_put_right:Nn \l__mformat_used_tl { ####1 }
% Define what the letter will now resolve to
\cs_set:cx { __mformat_letter_new_####1: } {
\exp_not:N ##1 {\mathchar\use:c { __mformat_mathcode_####1: }}
}
}
\char_gset_active_eq:nc { `####1 } { __mformat_letter_new_####1: }
} {
% Record that this is now an active character
\tl_gput_right:Nn \g__mformat_mathactive_tl { ####1 }
% Record that this has been used
\tl_put_right:Nn \l__mformat_used_tl { ####1 }
% Record the normal character so it can be used later
\cs_new:cx { __mformat_mathcode_####1: }
{ \the\mathcode`####1 }
% Define what the letter will now resolve to
\cs_new:cx { __mformat_letter_new_####1: } {
\exp_not:N ##1 {\mathchar\use:c { __mformat_mathcode_####1: }}
}
\char_gset_active_eq:nc { `####1 } { __mformat_letter_new_####1: }
% Set the character to be active in math mode
\char_set_mathcode:nn { `####1 } { "8000 }
}
}
% If there's no more character sets, finish, otherwise recurse
\tl_if_empty:nTF { ##3 } { } { \__mformat_parse:w ##3\relax }
}
% Begin recursive parsing
\__mformat_parse:w #1\relax
}
% \tl_show:N \l__mformat_remove_mathactive_tl
% Remove the active status from the characters that need it
\tl_map_inline:Nn \l__mformat_remove_mathactive_tl {
\tl_gremove_once:Nn \g__mformat_mathactive_tl {##1}
% Reset the math code
\char_set_mathcode:nn { `##1 } { \use:c { __mformat_mathcode_##1: } }
% Deregister functions
\cs_undefine:c { __mformat_letter_new_##1: }
\cs_undefine:c { __mformat_mathcode_##1: }
}
}
\NewDocumentCommand{\std}{m}{ \mathchar\use:c { __mformat_mathcode_#1: } }
\ExplSyntaxOff
Which is used like
\mformat{\mathbb[R]\mathbf[vw]}



a^{b^c}becomesa^b^cand boom. If you want to go the token-by-token route, I suggest\peek_analysis_map_inline:n, though maybe it's easier to makevmath-active instead... – Phelype Oleinik May 12 '22 at 02:01vmath-active? Is that a variant of the macro or something else. – Kevin Fisher May 12 '22 at 02:07vprints\mathbf{v}. The basic idea is in the third snippet here, and a friendly interface is\DeclareMathActivefrom here. – Phelype Oleinik May 12 '22 at 02:20