5

I try to find a way actually I get

update :

\documentclass{article}
\def\IsArgaCs#1{%
\ifcat\relax\noexpand#1true \else false \fi%
}%
\begin{document}
\def\a{a}%
\let\b=~%
\def\c{1}%
1)\IsArgaCs{\a}%    
2)\IsArgaCs{a}%
3)\IsArgaCs{\b}%
4)\IsArgaCs{\c}%
\def\name{A}%
\def\i{1}%
5)\IsArgaCs{\name\i}%
\let\d=z %
6)\IsArgaCs{\d}%
\end{document}

enter image description here

But I'm not sure if this test is correct. I need this test because I want to know if an argument is the name of a node like "A" or a macro like `\startpoint' that defines the coordinates of a node.

I read this how-to-check-if-token-is-a-macro but it's very complicated.

Complement :

example of a code :

\makeatletter
\newdimen\pt@xa
\newdimen\pt@ya
\def\SavedPoint#1#2{%
    \pgfextractx{\pt@xa}{\pgfpointanchor{#2}{center}}%
    \pgfextracty{\pt@ya}{\pgfpointanchor{#2}{center}}%
    \pgfextract@process\tkzsavepoint{\pgfpoint{\pt@xa}{\pt@ya}}%
    \global\expandafter\edef\csname #1\endcsname{\tkzsavepoint}% 
}
\def\macro#1#2#3{%
% if #1 a node get the coordinates
% else #1 is a macro defined by \SavedPoint get the coordinates
% I'm not sure that it's possible to get with the same way the coordinates
% etc. idem for #2 #3
% then work with the coordinates 
}
\makeatother

\path   coordinate (a) at (0,1)
        coordinate (b) at (1,2)
        coordinate (c) at (2,3);

        \SavedPoint{pta}{a}
        \SavedPoint{ptb}{b}
        \SavedPoint{ptc}{c}

\macro{a}{b}{c} or \macro{\pta}{\ptb}{\ptc}
 or  a mix \macro{a}{\ptb}{\ptc}

 possible \x\i  for the nodes for example inside a loop \foreach
Alain Matthes
  • 95,075
  • If the argument is two tokens or more you're in troubles. You don't need \noexpand before \relax (assuming nobody is redefinining \relax); the test is good for one token arguments, but does not catch active characters as being macros, which may not be what you want. It also doesn't catch control sequences \let to a character. – egreg May 01 '16 at 19:59
  • @egreg So my test is dangerous ! Logically I use one token arguments but I'm not sure if the case with two tokens is impossible ... – Alain Matthes May 01 '16 at 20:09
  • So you should be clearer about what you expect in case the argument consists of multiple tokens. The code, as it stands, adds unwanted space tokens, but that's easy to take care of. – egreg May 01 '16 at 20:12
  • @egreg In my code I can avoid the problem but it's possible to see a user for example define a point with \name\i with \def\name{A} \def\i{1}. And it's a problem. Control sequences \let to a character are not possible. You are right about the unwanted space tokens but in my context (tikz) they are removed but I should pay attention. – Alain Matthes May 01 '16 at 20:50
  • If the argument eventually expands to a node name, it might be convenient to just fully expand it. – egreg May 01 '16 at 20:55
  • @egreg Yes right again but in this case at what point do you think I need to expand it ? – Alain Matthes May 01 '16 at 21:08
  • Without knowing how you want to use this… – egreg May 01 '16 at 21:14
  • What about \stringify the token and then check if the expansion begins with \escapechar? – GuM May 01 '16 at 21:21
  • @egreg I try to give you an example of code in the question – Alain Matthes May 01 '16 at 21:44

4 Answers4

5

If you mean that control sequence is something what begins with backslash (irrelevant what meaning it has) than you can try following code. Note that this code checks all alternatives (including spaces, more tokens in the argument or {}).

{\escapechar=-1\xdef\nb{\string\\}} % normal backslash
\def\IsArgaCs#1{\futurelet\next\isargacsA#1\end}
\def\isargacsA{\def\nextB{\expandafter\isargacsB\string}%
   \expandafter\ifx\space\next \def\nextB{\isargacsB.}\fi
   \ifx\next\bgroup \def\nextB{\expandafter\isargacsC\string}\fi
   \ifx\next\end \def\nextB{\isargacsB.}\fi
   \nextB
}
\def\isargacsB#1#2\end{\if\nb#1TRUE\else FALSE\fi}
\def\isargacsC#1{%
   \if\nb#1\def\nextB{\isargacsB\nb}\else
      \def\nextB{\expandafter\isargacsB\expandafter.\expandafter}%
   \fi
   \nextB{\iffalse}\fi
}

1)\IsArgaCs{\a}
2)\IsArgaCs{a}
3)\IsArgaCs{\b\c\d}
4)\IsArgaCs{\c}
\def\name{A}
\def\i{1}
5)\IsArgaCs{\name\i}
\let\d=z 
6)\IsArgaCs{\d}
7)\IsArgaCs{~}
8)\IsArgaCs{ }
9)\IsArgaCs{{}}

Edit: I've improved the test of \bgroup because \bgroup must return TRUE but real open brace must return FALSE. The trick with relatively balanced braces must be used.

wipet
  • 74,238
4

Are you really sure you wish to check for control sequence tokens?

Primitives like \relax are not macros but they are control sequences.

Active characters (character tokens of category code 13) are not control sequence tokens but they can be defined to be macros.

A control sequence token or an active character token whose meaning denotes that it is undefined also is not a macro.

The case of the argument in question being empty also is a case where no macro/no control sequence token is present.

Arguments starting with opening curly braces /catcode-1-characters might require special attention.

The following code provides testing by checking the meaning of the first token of the argument in question in case that argument is not empty.

Leading space tokens and leading catcode-1-characters are taken into account.

%%===============================================================
%% \firstoftwo and \secondoftwo:
%%...............................................................
\long\def\firstoftwo#1#2{#1} \long\def\secondoftwo#1#2{#2}
%%---------------------------------------------------------------
%% \AtIfArgsFirstTokenIsMacro - takes three macro-arguments and
%%              forks depending on whether the first argument
%%              holds a first/leading token whose \meaning equals
%%              one of the phrases "macro:", "\long macro:".
%%              If so delivers the second argument, otherwise
%%              delivers the third argument.
%%
%%              The macro is suitable for expansion contexts
%%              and due to \romannumeral-expansion delivers the
%%              result after two expansion steps/after "being
%%              hit by two \expandafter-chains".
%%
%%              The case of the first argument being empty/not
%%              holding any token at all is considered a case
%%              where the argument does not hold a first/leading
%%              token at all and thus does not hold a first/leading
%%              token whose meaning equals one of the phrases
%%              "macro:", "\long macro:".
%%
%%              The test does not rely on some token being
%%              undefined. eTeX- or whatsoever extensions are not
%%              required.
%%
%%              I assume there is room for improvement/shortening
%%              the code.
%%
%%              Testing by means of macros for a leading
%%              \outer-macro-token in the argument is somehat
%%              obsolete and impossible as \outer-tokens cannot
%%              occur inside macro-arguments.
%%
%%...............................................................
\def\AtIfArgsFirstTokenIsMacro#1#2{%
  \long\def\AtIfArgsFirstTokenIsMacro##1##2##3{%
    \romannumeral\iffalse{\fi\expandafter\secondoftwo\expandafter
    {\expandafter{\string##1}\expandafter\expandafter\expandafter
    \firstoftwo\expandafter\expandafter\expandafter\firstoftwo
    \expandafter\secondoftwo\expandafter{\expandafter{\iffalse}}\fi
    \expandafter\secondoftwo\string}\expandafter\firstoftwo
    \expandafter{\iffalse}\fi\iffalse{\fi
    \expandafter\innerAtIfArgsFirstTokenIsMacro\meaning##1#1}{}%
      {%
        \iffalse{\fi
        \expandafter\innerAtIfArgsFirstTokenIsLong\meaning##1#2 #1}{}%
      }{\secondoftwo}%
    }{\firstoftwo}%
    {0 ##3}{0 ##2}%
  }%
  \long\def\innerAtIfArgsFirstTokenIsMacro##1#1{%
    \innerAtIfArgsFirstTokenIs{##1}%
  }%
  \long\def\innerAtIfArgsFirstTokenIsLong##1#2 #1{%
    \innerAtIfArgsFirstTokenIs{##1}%
  }%
  \long\def\innerAtIfArgsFirstTokenIs##1{%
    \iffalse{\fi\expandafter\secondoftwo\expandafter{\string##1}%
    \expandafter\firstoftwo\expandafter{\iffalse}\fi\expandafter
    \expandafter\expandafter\firstoftwo}{\expandafter\expandafter
    \expandafter\secondoftwo}\expandafter\secondoftwo\expandafter
    {\iffalse}\fi
  }%
}%
% Somehow get the catcode-12-token-phrases "macro:" and "\long"
% as argument of \AtIfArgsFirstTokenIsMacro - hereby
% it is relied on the usual catcode settings and on primitives not
% being redefined and on integer parameters like \globaldefs and
% \endlinechar and \escapechar holding usual values, thus:
\begingroup
\edef\AtIfArgsFirstTokenIsMacro{%
  {\string m\string a\string c\string r\string o%
  \string :}{\string\long}%
}%
\expandafter\endgroup%
\expandafter\AtIfArgsFirstTokenIsMacro%
\AtIfArgsFirstTokenIsMacro%
%%===============================================================

\def\test{defined}

\long\def\testb{defined}

\catcode`\Z=13

1 \AtIfArgsFirstTokenIsMacro{\UndFINeD}{Macro}{Not Macro} % OK

2 \AtIfArgsFirstTokenIsMacro{\undefined A}{Macro}{Not Macro} % OK

3 \AtIfArgsFirstTokenIsMacro{\undefined A\fi}{Macro}{Not Macro} % OK

4 \AtIfArgsFirstTokenIsMacro{}{Macro}{Not Macro}  % OK

5 \AtIfArgsFirstTokenIsMacro{Z}{Macro}{Not Macro} % OK

6 \AtIfArgsFirstTokenIsMacro{\relax}{Macro}{Not Macro} % OK

7 \AtIfArgsFirstTokenIsMacro{\TeX}{Macro}{Not Macro} % OK

8 \AtIfArgsFirstTokenIsMacro{\test}{Macro}{Not Macro} % OK

9 \AtIfArgsFirstTokenIsMacro{\par}{Macro}{Not Macro} % Evaluate/OK

10 \AtIfArgsFirstTokenIsMacro{\fi}{Macro}{Not Macro} % OK

11 \AtIfArgsFirstTokenIsMacro{\else}{Macro}{Not Macro} % OK

12 \AtIfArgsFirstTokenIsMacro{\if}{Macro}{Not Macro} % OK

13 \AtIfArgsFirstTokenIsMacro{#}{Macro}{Not Macro} % OK

14 \AtIfArgsFirstTokenIsMacro{ }{Macro}{Not Macro} % OK

15 \AtIfArgsFirstTokenIsMacro{\endcsname}{Macro}{Not Macro} % OK

16 \AtIfArgsFirstTokenIsMacro{\AtIfArgsFirstTokenIsMacro}{Macro}{Not Macro} %Long Macro

17 \AtIfArgsFirstTokenIsMacro{\bgroup}{Macro}{Not Macro}% OK

18 \AtIfArgsFirstTokenIsMacro{\egroup}{Macro}{Not Macro}% OK

\letZ=\relax

19 \AtIfArgsFirstTokenIsMacro{Z}{Macro}{Not Macro}% OK

20 \AtIfArgsFirstTokenIsMacro{\testb}{Macro}{Not Macro}% OK

21 \AtIfArgsFirstTokenIsMacro{\testb bla bla}{Macro}{Not Macro}% OK

22 \AtIfArgsFirstTokenIsMacro{bla \fi}{Macro}{Not Macro}% OK

23 \AtIfArgsFirstTokenIsMacro{several tokens}{Macro}{Not Macro}% OK

24 \AtIfArgsFirstTokenIsMacro{A}{Macro}{Not Macro}% OK

25 \AtIfArgsFirstTokenIsMacro{{\undefined A}\fi}{Macro}{Not Macro}% OK

26 \csname\AtIfArgsFirstTokenIsMacro{\tEx}{tEx}{TeX}\endcsname% OK

27 \if0\AtIfArgsFirstTokenIsMacro{\tEx}{0}{1}\tEx \else \TeX\fi% OK

\bye
Ulrich Diez
  • 28,770
2

The tokcycle package has reasonably extensive ability to glean the characteristics of a token, as shown in this MWE. In lines 1.-7., I show some of these abilities. In lines 8a. and 8b., I propose how to find a macro, which I define here as an expandable control sequence.

\documentclass{article}
\usepackage{tokcycle}
\newcommand\characteristics{%
  \ifactivetokunexpandable Unexpandable-Active-Token \else
    \ifactivetok Active-Token \fi\fi
  \ifactivechar Active-Char-Code \fi
  \ifimplicittok Implicit \fi
  \ifcatSIX Cat-6 \fi
}
\newcommand\IsArgaCs[1]{%
  \tokencycle{\characteristics}{}{Control Sequence \characteristics}%
  {Space \characteristics}#1\endtokencycle}
\parindent 0pt
\begin{document}
1: \IsArgaCs a\par
2: \IsArgaCs\relax\par
\let\littlet t
3: \IsArgaCs \littlet\par
\def\tmp{Q}
\catcode`Q=\active
\def Q{X}
4a: \IsArgaCs Q\par
\let QX
4b: \IsArgaCs Q\par
4c: \expandafter\IsArgaCs\tmp\par
\catcode`Q=11 
4d: \IsArgaCs Q\par
5a. \IsArgaCs #\par
\let\svhash#
5b. \IsArgaCs\svhash\par
6. \IsArgaCs\space\par
\makeatletter
7. \IsArgaCs\@sptoken\par
\smallskip
One can determine macro-ness by letting active character to the test token
  and testing the active character.  If not a macro, it comes up as 
  unexpandable:\\
\catcode`Q=\active
\let Q\relax
8A: \IsArgaCs Q\par
\let Q\today
8B: \IsArgaCs Q\par
\end{document}

enter image description here

1

In case you wish to know about control sequence tokens, not about tokens whose meaning denotes them to be macros:

The result of applying \string to a control sequence token is affected by the value of the integer parameter \escapechar.

Have TeX apply \string with different values of \escapechar and check if the results differ.

Make sure the arguments of stringification are not empty by applying a trailing character token.

Make sure not to apply stringification to an opening brace yielding a construct of unbalanced braces.

\AtIfControlSequence checks whether argument's first token is a control sequence token.

\AtIfControlSequence is not expandable / is not suitable for expansion-contexts as it performs assignments within a local scope. For holding the assignments within the local scope, \AtIfControlSequence relies on \globaldef's value being 0.

%%----------------------------------------------------------------------
%% Paraphernalia:
%%......................................................................
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
%%----------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a
%% catcode-1-character-token, e.g., an opening brace:
%%......................................................................
\long\def\CheckWhetherLeadingBrace#1{%
  \expandafter\secondoftwo\expandafter{\expandafter{\string#1.%
  }\expandafter\firstoftwo\expandafter{\expandafter\firstoftwo
  \expandafter\firstoftwo\expandafter}\string}\secondoftwo
}%
%%----------------------------------------------------------------------
%% Check whether argument's first token is a control-sequence
%% This test is _not_ expandable / is _not_ suitable for
%% expansion-contexts as it performs assignments.
%% Also it relies on \globaldef's value being 0.
%%......................................................................
\long\def\AtIfControlSequence#1{%
  \CheckWhetherLeadingBrace{#1}{\secondoftwo}{%
    \begingroup
    \escapechar=`\A\relax
    \expandafter\def\expandafter\tempa\expandafter{\string#1.}%
    \escapechar=`\B\relax
    \expandafter\def\expandafter\tempb\expandafter{\string#1.}%      
    \ifx\tempa\tempb
      \expandafter\endgroup\expandafter\secondoftwo
    \else
      \expandafter\endgroup\expandafter\firstoftwo
    \fi      
  }%
}%

1 \AtIfControlSequence{\relax}{}{not a }control sequence token

2 \AtIfControlSequence{{\relax}}{}{not a }control sequence token

3 \AtIfControlSequence{relax}{}{not a }control sequence token

4 \AtIfControlSequence{}{}{not a }control sequence token

5 \AtIfControlSequence{ \relax}{}{not a }control sequence token

\bye 
Ulrich Diez
  • 28,770