The requirement of things working out when used in the argument of \setpgfmath implies that things should work in situations also where everything is done only by means of expansion/where everything is done already during the stage of expansion.
Therefore having every object-function define a scratch-macro denoting the name of the object where it belongs to is not an option.
How about \NewObject replacing in its 2nd argument each instance of the token \this by #1/the name of the object before further processing the tokens coming from the 2nd argument?
Of course, with this approach \this cannot be masked, e.g., as \csname this\endcsname or as \foobar after \let\foobar=\this.
Also the replacement is only bound to \this-tokens that are components of \NewObject's second argument.
Therefore something like
\def\macrodefinitionhidesthis{Something with \this\var}
\let\foobar=\this
\NewObject{MyNewObject}{%
\NewObjectCommand{\var}{}{My Object's var}%
\NewObjectCommand{\whatsoever}{}{%
...
\macrodefinitionhidesthis
...
\csname this\endcsname\var
...
\foobar\var
}%
}
will not work out.
In case you are nonetheless interested in the "replace-\this-by-object's-name-at-definition-time"-route:
At first glimpse \tl_replace_all:Nnn looks promising.
But it isn't because the replacement is not done inside brace-groups. :-)
Probably something can be done by means of the features of the package l3regex which is part of expl3 and therefore is described in interface3.pdf. But I did not yet delve into that.
Off the cuff I can just offer a simple tail-recursive expandable routine
\ReplaceThis{⟨\this-replacement⟩}%
{⟨tokens where the token \this shall be replaced by ⟨\this-replacement⟩⟩}%
where replacement is done inside brace-groups as well.
Due to \romannumeral-expansion the routine does deliver the result after two expansion-steps/after two "hits" by \expandafter.
The gist of the routine is:
The routine initiates a tail-recursive loop \UD@ThisReplaceloop.
I.e. \UD@ThisReplaceloop is a macro which calls itself again and again with its arguments being modified after each iteration until all the replacement is done.
The tail-recursive \UD@ThisReplaceloop-macro handles three arguments:
- One argument
⟨\this-replacement⟩ - this argument holds the tokens that shall replace the token \this.
- Another argument holding the
⟨tokens where the token \this shall be replaced by ⟨\this-replacement⟩⟩.
- Yet another argument holding the
⟨tokens forming the replacement-result gathered so far⟩.
The tail-recursive \UD@ThisReplaceloop-macro causes TeX to "look" at the first token of the ⟨tokens where the token \this shall be replaced by ⟨\this-replacement⟩⟩-argument:
If that argument is empty, the job is done, thus the ⟨tokens forming the replacement-result gathered so far⟩ are delivered.
If the first token of that argument is a space-token, then that token cannot be handled as an undelimited argument. Instead have TeX
- remove it by means of a macro that gobbles a space-delimited argument and
- append a space to the
⟨tokens forming the replacement-result gathered so far⟩ and
- do the loop again.
If the first token of that argument is not a space-token, then there is something that can be handled as an undelimited argument.
If the first token of that argument is not a curly brace, have TeX
- extract/remove the "thing" that can be handled as undelimited argument from that argument and
- in case that thing does not equal the token
\this append that thing to the ⟨tokens forming the replacement-result gathered so far⟩
- in case that thing does equal the token
\this append ⟨\this-replacement⟩ to the ⟨tokens forming the replacement-result gathered so far⟩
- do the loop again.
If the first token of that argument is a curly brace, have TeX
- extract/remove the "thing" that can be handled as undelimited argument from that argument and
- append, nested in a pair of curly braces, to the
⟨tokens forming the replacement-result gathered so far⟩ the result of applying the replacement-routine to that thing
- do the loop again.
The drawback of that routine is:
As side-effect any matching pair of explicit character tokens of catcode 1(begin group) and 2(end group) is replaced by a matching pair of explicit character-tokens {1 and }2.
Under normal circumstances { is the only character of category code 1 and } is the only character of category code 2.
So under normal circumstances you don't get explicit character tokens of catcode 1 other than {1 and you don't get explicit character tokens of catcode 2 other than }2.
So under normal circumstances this side-effect doesn't matter.
But it probably might bite you in exceptional situations where explicit character tokens of catcode 1 other than {1 and/or explicit character tokens of catcode 2 other than }2 are created deliberately, e.g., via \catcode-assignments, e.g., via \uppercase/\lowercase-trickery, for usage in situations where arguments delimited by an explicit character token of catcode 1 other than {1 are processed. Macros that process such arguments can be defined via #⟨character of category code 1⟩-notation; one of the more obscure things described in some dangerous-bend-paragraph of the TeXbook. :-)
At first glimpse all the "\romannumeral-driven-expansion-before-exchanging-arguments"-trickery may seem confusing to the beginner. If so, then don't be frightened. It all will seem quite simple as soon as you are just a little bit more familiar to the concept of expansion. :-)
The gist of \romannumeral-expansion is:
While gathering the number to convert \romannumeral triggers expanding things until it is obvious that (either) all tokens/digits belonging to that number are gathered (or an error-message needs to be raised).
If the number gathered for converting is not positive, then \romannumeral-conversion does not deliver any tokens. The tokens forming that number are just swallowed silently.
Thus you can use \romannumeral for triggering a lot of expansion- and macro-argument-exchanging-work as long as it is ensured that in the end the first thing of the result of that work is a sequence of tokens that forms a non-positive number. This sequence will be removed. The tokens forming the remainder of the result of that work will be processed as usual.
I'm sorry I did not yet find the time to re-implement the sub-components of \ReplaceThis in terms of expl3. :-)
\errorcontextlines=10000
\makeatletter
%%///////// Code for \ReplaceThis /////////////////////////////////////////////
%% Syntax:
%% -------
%%
%% \ReplaceThis{<\this-replacement>}%
%% {<tokens where \this shall be replaced by <\this-replacement>>}%
%%
%% The result is delivered after two expansion-steps/by two "hits"
%% with \expandafter.
%%
%% As a side-effect any matching pair of explicit character tokens
%% of category code 1 and 2 is replaced by a matching pair of
%% explicit character-tokens {_1 and }_2.
%%
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@removespace, \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingExplicitSpace,
%% \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%-----------------------------------------------------------------------------
%% -- Take advantage of the fact that TeX discards space tokens when
%% "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is blank>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not blank>}%
\newcommand\UD@CheckWhetherBlank[1]{%
\romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}{}}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has a leading
%% explicit catcode-1-character-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not have a
%% leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
\expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does have a
%% leading explicit space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does not have a
%% a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign/
% tabular-environment:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral\expandafter\UD@secondoftwo
\string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
}{}%
}%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
\long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {{AB}}
%%
%% Due to \romannumeral-expansion the result is delivered after two
%% expansion-steps/after "hitting" \UD@ExtractFirstArg with \expandafter
%% twice.
%%
%% \UD@ExtractFirstArg's argument must not be blank.
%% This case can be cranked out via \UD@CheckWhetherBlank before calling
%% \UD@ExtractFirstArg.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% Frozen-\relax is chosen because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
{\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral\expandafter
\UD@PassFirstToSecond\expandafter{\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1}%
}{%
\UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
}%
}{%
\newcommand\UD@ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@stopromannumeral#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%====================================================================
\@ifdefinable\UD@ThisInstance{\long\def\UD@ThisInstance#1\this#2#3!{#2}}%
\newcommand\UD@ReplaceThisInstance[2]{%
% #1 a single token to examine
% #2 \this-replacement
\UD@ThisInstance#1{\UD@stopromannumeral#2}\this{\UD@stopromannumeral#1}!%
}%
\newcommand\UD@ThisReplaceloop[3]{%
% #1 replacement for \this
% #2 tokens forming the result gathered so far
% #3 remaining token list to process
\UD@CheckWhetherNull{#3}{\UD@stopromannumeral#2}{%
\expandafter\UD@Exchange\expandafter{%
\romannumeral
\UD@CheckWhetherLeadingExplicitSpace{#3}{%
\expandafter\UD@PassFirstToSecond
\expandafter{\UD@removespace#3}{\UD@stopromannumeral{#2 }}%
}{%
\expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter{%
\expandafter\UD@Exchange\expandafter{%
\romannumeral
\UD@CheckWhetherBrace{#3}{%
\expandafter\UD@stopromannumeral\expandafter{\romannumeral
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\UD@ExtractFirstArg{#3}{\UD@ThisReplaceloop{#1}{}}%
}%
}{%
\expandafter\expandafter\expandafter\UD@ReplaceThisInstance
\UD@ExtractFirstArg{#3}{#1}%
}%
}%
{#2}%
}%
}%
}%
}{\UD@ThisReplaceloop{#1}}%
}%
}%
\newcommand\ReplaceThis[2]{%
% #1 \this-replacement
% #2 tokens where to \replace \this by \this-replacement
\romannumeral\UD@ThisReplaceloop{#1}{}{#2}%
}%
%%///////// End of code for \ReplaceThis //////////////////////////////////////
\makeatother
\documentclass{article}
\usepackage{expl3}
\usepackage{xfp}
\usepackage{tikz}
\ExplSyntaxOn
\NewDocumentCommand\NewDocumentCommandName{}{\exp_args:Nc \NewExpandableDocumentCommand}
\tl_new:N \l__object_path_tl
\NewDocumentCommand\NewObject{ m }{
\tl_set:Nn \l__object_path_tl {\cs_to_str:N #1}
\newcommand#1[1]{\use:c {\cs_to_str:N #1 \cs_to_str:N ##1}} % I don't recommend using the scratch-variable
% \l__object_path_tl for \cs_to_str:N #1
% insise the definition of <object> for the following
% reason:
% The invariant condition for that scratch-variable is:
% At the time of defining an <object> it denotes the
% name of the <object>.
% But at the time of carrying out an <object>'s
% command <object> that variable may have whatsoever
% value not denoting the name of the <object>
% in question.
\ReplaceThis{#1}%
}
\NewDocumentCommand\NewObjectCommand{ m }{
\NewDocumentCommandName{\l__object_path_tl \cs_to_str:N #1}
}
\ExplSyntaxOff
% This is not in \ExplSyntax any more, thus you should get used to taking care of the
% \endlinechar-mecnanism yielding space-tokens at end of lines in case lines end with
% something that switches TeX's reading apparatus to state M(middle of line), e.g.,
% explicit non-space-character-tokens, control-symbol-tokens other than control-space, ...
% In horizontal mode these space-tokens might yield undesired horizontal glue.
%
% You are in luck that all your \NewObject-commands are carried out in the
% preamble where TeX still is in vertical mode where horizontal glue doesn't matter.
%
% But not caring about the \endlinechar-mechanism might bite you when defining
% objects while TeX is in horizontal-mode.
\NewObject{\obja}{%%%%
\NewObjectCommand{\var}{}{var obj~A}%%%%
}%%%%
\NewObject{\objb}{%%%%
\NewObjectCommand{\var}{}{var obj~B}%%%%
\NewObjectCommand{\func}{O{}m}{func obj~B with #1, #2, \obja\var\ and \this\var}%%%%
\NewObjectCommand{\refobja}{}{\obja}%%%%
\NewObjectCommand{\ref}{m}{\csname#1\endcsname}%%%%
}%%%%
\NewObject{\objc}{%%%%
\NewObjectCommand{\vara}{}{1}%%%%
\NewObjectCommand{\varb}{}{10}%%%%
\NewObjectCommand{\varc}{}{\fpeval{100 + \this\vara + \this\varb}}%%%%
}%%%%
\begin{document}
\noindent
document:\
\verb|\obja\var|: \obja\var\
\verb|\objb\var|: \objb\var\
\verb|\objb\func[oarg]{marg}|: \objb\func[oarg]{marg}\ % With \NewExpandableDocumntCommand
% the last argument cannot be an optional one.
% See the xparse-manual.
\verb|\objb\refobja\var|: \objb\refobja\var\
\verb|\objb\ref{obja}\var|: \objb\ref{obja}\var\
\verb|\objb\ref{objb}\var|: \objb\ref{objb}\var\
\verb|\objb\ref{objc}\varc|: \objb\ref{objc}\varc\
xfp:\
\verb|\objc\varc|: \objc\varc\
\verb|\fpeval{100 + \objc\vara + \objc\varb}|: \fpeval{100 + \objc\vara + \objc\varb}\
tikz/pgf:\
\verb|\pgfmathsetmacro{\varpgf}{100 + \objc\vara + \objc\varb}\varpgf|: \pgfmathsetmacro{\varpgf}{100 + \objc\vara + \objc\varb}\varpgf\
\verb|\tikz \draw (0, 0) -- (\objc\vara + 1, 0); |: \tikz \draw (0, 0) -- (\objc\vara + 1, 0);
\end{document}

$\accent{symbol}_{subscript}^{superscript}$. Because the parts are stored in separate object commands it is possible to append e.g. subscripts afterwards. – Michael Aug 23 '21 at 16:30thisis really just#1. (That's after having tried pgf's oop implementation). I can link to some examples of its use if that sounds a useful approach. – Andrew Stacey Aug 23 '21 at 17:59\NewObjectCommandare actually not used at all by\NewObjectCommand. They are just passed to\NewDocumentCommandName. Thus you can define\NewObjectCommandas a single-argument-macro just doing\NewDocumentCommandName{\l__object_path_tl \cs_to_str:N #1}.\NewDocumentCommandNamewill "grab" subsequent arguments anyway. :-) – Ulrich Diez Aug 24 '21 at 16:01\NewObjectreplacing in its 2nd argument each instance of the token\thisby#1before processing #2? – Ulrich Diez Aug 24 '21 at 16:03\let\this=#1? – Andrew Stacey Aug 24 '21 at 17:10\letis an assignment. Assignments are not carried out during the stage of expansion (but afterwards). Thus functions/macros whose definitions start with\let...cannot be used in situations where everything is to be done only by means of expansion. But the questioner requests functions to work in such situations, i.e., when\pgfsetmathevaluates its argument. Other such situations are, e.g., tokens in the argument of\message/\special/\texorpdfstring, definition-text of an\edef, tokens between\csname..\endcsname, gathering a TeX-\pgfmathsetmacro{\pgfvar}{\pgfobj.func(...)}is not possible, which probably corresponds to the expandability requirement that @Ulrich Diez described. In this context thanks to @Ulrich Diez and @Andrew Stacey for their discussion. – Michael Aug 28 '21 at 13:53