Question
Is there a way to check whether a macro is fully expandable (or rather "safe in an expansion-only context" [1])?
Consider this code:
\def\a{Just a string}
\def\b{\a}
\def\c{\def\unsafe}
\def\d{\c}
How could I check which of the macros (a-d) are safe in an expansion-only context? By looking at them I know that a and b are whereas c and d are not but if I wanted to know the same for a macro I haven't written myself this could get quite useful.
Background
I am working on a way to detect whether some input is a valid number in PGF. For this I developed this approach which makes use of passing the input into \pgfmathfloatparsenumber.
The problem I have run into is that said macro appears to somehow manages to expand the input until there is an error (if the input is in fact not safe in an expansion-only-context). I tried using protected, noexpand and similar but somehow PGF manages to circumvent those.
So the idea is to check whether the input is safe before actually passing it to PGF. The problem is: I don't know how I'd go about that...
\unsafefirst then the edef would give something bad but would probably not give an error during the actual edef, such cases you can probably detect. similarly if you have\edef\foo{ {\mbox} }you are going to get a low level parse error if you expand \mbox and it hits the}it woul dbe verh hard to avoid such errors if you allow bad input – David Carlisle Dec 13 '18 at 08:11\romannumeral-`0triggered expansion, then examine first token if a digit ok remove and repeat and do repetitively until either nothing is left or you hit some unexpandable token which is not a digit. You have to detect case of braces etc... The idea here is that\edefcan cause errors if your material is not expandable, but "full-first" expansion will not. – Dec 13 '18 at 08:46\IfDecimalfrom thexstringdecimal and I'd be good. The problem is that there might be letters as well (scientific number notation)... – Raven Dec 13 '18 at 08:49xintfracpackage allows a much wider notion of input than the ones for which\IfDecimal(applied to full expansion) will test positive. Andxintfracproceeds purely expandably. If you hack into it you can add branches for the cases it detects something went awry and you could convert it into something which detects if input expands to the expected format. Even more powerful is thexintexprparser mechanism. – Dec 13 '18 at 08:53\romannumeral:) – Skillmon Dec 13 '18 at 08:54xintfracinput processing because at some locations it uses\numexprhence unrecoverable low-level errors, thus more something in the style ofxintexpr(although at some locations it applies\stringand also must act expandably). Anyway, the idea of repeated\romannumeral-`0expansion is workable. – Dec 13 '18 at 08:56\romannumeral-`0will expand macros which have been defined via\protected\def, so this is not really same as doing what would happen in an\edef. Also careful with spaces when doing this things. One can not simply do the expansion then grab a token. If you don't need to work expandably then\futureletcan help. – Dec 13 '18 at 09:03\meaningof each token whether it is\protected. – Skillmon Dec 13 '18 at 09:07"x""e" type recently added to LaTeX3 is surely a reference implementation, and the code comments will be helpful. But in a way any parser such as xfp or xintexpr basically has the query already implemented. They proceed purely expandably and I am sure xfp raises more readable errors in case of problems... (by the way I know my comments go in various directions but it is hard to stay focused if the exact context is not known: does the thing have to work expandably for example?) – Dec 13 '18 at 09:10xis\edefexpansion, do you meanetype? – Skillmon Dec 13 '18 at 09:14\romannumeralexpansion step one would first check for spaces and braces, then for\protectedand then for valid numbers, discarding them and relooping until either one of the tests is false or the full string was discarded as valid numbers -- additionally testing for at most one decimal marker and an optional lower or upper caseewhich could be of category code 11 or 12). – Skillmon Dec 13 '18 at 09:18\romannumeral-`0which would gobble it and stop. This is also why context is important:xintexprdoes not care and ignore spaces, but perhaps here you don't want to allow them in input. Or you do... – Dec 13 '18 at 09:21\romannumeral, too. – Skillmon Dec 13 '18 at 09:24\romannumeralexpansion. That space would be gobbled but we would like to say that this is a case where the test should result in false. So we'd have to parse the\meaningof each token not only for\protected, but also for a space. – Skillmon Dec 13 '18 at 09:46\romannumeralexpansion easily if we care for spaces.\Foomay expand to\foo\barwith\fooexpanding to a space token. The\meaningof\Foo(and imagine if\escapecharis -1...) will at best tell us there is\foo. If we blindly apply romannumeral expansion, it will expand the foo and silently gobble the space (we can not distinguish if\fooexpand to1or to<space>1). Thus we need to check the meaning of\footoo. And we have to do this recursively until a non-expandable token. Then we go back and apply romannumeral to force expansion of \Foo – Dec 13 '18 at 10:08\expandafter\baz\foodoes expand\fooeven if was\protectedso we can't blindy repeat\expandafterif we care for that, meaning (sic) we must use the\meaning, which requires to properly grab a token i.e. check for braces, spaces...) – Dec 13 '18 at 10:16