Here is one solution I can think of which uses just plain TeX.
\def\checkspace{\catcode`\ =12\relax\futurelet\csps\checkspaceA}
\def\checkspaceA{\catcode`\ =10\relax\if\space\csps\ \afterassignment\ignorespaces\expandafter\let\expandafter\csps\fi}
\def\foo{foo\checkspace}
test \foo bar
test foo bar
test \foo.
\bye
The idea is like so: \checkspace
first makes spaces other so that they are not skipped and can be \futurelet
'd.
Then we inspect the next token in \checkspaceA
using \futurelet
.
\checkspaceA
makes spaces space tokens again, and if the token we are inspecting is a space, then a \
is inserted.
The purpose of the rest of the code inside the conditional in \checkspaceA
is to make sure that if \checkspace
is followed by multiple spaces, all the spaces following the first are ignored.
First I will explain the issue (to my understanding) so I can better explain my solution.
If we were to remove this code from \checkspaceA
and do the following test:
\def\checkspaceA{\catcode`\ =10\relax\if\space\csps\ \fi}
test \foo bar
Then we would get test foo
followed by a space, then an other space character (which looks like a slanted dash in cmr), then bar
.
The issue is that once we do \futurelet\csps\checkspaceA
in \checkspace
, the token following \foo
is tokenized into an other space token, and left in the stream, and this is what creates the weird dash character.
So we can alter the code slightly to get rid of this:
\def\checkspaceA{\catcode`\ =10\relax\if\space\csps\ \expandafter\let\expandafter\csps\fi}
test \foo bar
But now the issue is that since there are two spaces following \foo
, the first one is correctly converted into \
by \checkspaceA
.
But the next space token really followed an other token, and is therefore not ignored.
Doing \afterassignment\ignorespaces
before the \let
ensures that it is ignored.
This solution will not work for any character with catcode 10, and setting up the macros so that they work for other characters requires changing the catcodes of multiple characters and nesting \if
s.
In general I don't really see a reason to for these macros, I think it may just be simpler to follow your macros with an empty group.
You almost certainly won't run into weird unintended side effects doing so.
I'm not 100% sure that my macros will always work, if there is an issue with them, feel free to tell me and I'd be happy to attempt to fix them.