3

I want to create classes/objects using normal LaTeX commands to represent member variables and member functions. I came up with the following approach:

\documentclass{article}

\usepackage{expl3} \usepackage{xfp} \usepackage{tikz}

\ExplSyntaxOn \NewDocumentCommand\NewDocumentCommandName{}{\exp_args:Nc \NewDocumentCommand} \tl_new:N \l__object_path_tl \NewDocumentCommand\NewObject{m m}{ \tl_set:Nn \l__object_path_tl {\cs_to_str:N #1} \NewDocumentCommand#1{m}{\use:c {\cs_to_str:N #1 \cs_to_str:N ##1}} % TODO Use \l__object_path_tl for \cs_to_str:N #1 #2 } \NewDocumentCommand\NewObjectCommand{m m m}{ \NewDocumentCommandName{\l__object_path_tl \cs_to_str:N #1}{#2}{#3} } \ExplSyntaxOff

\NewObject{\obja}{ \NewObjectCommand{\var}{}{var obj~A} } \NewObject{\objb}{ \NewObjectCommand{\var}{}{var obj~B} \NewObjectCommand{\func}{m O{}}{func obj~B with #1, #2, \obja\var\ and \objb\var} % TODO Allow to use \this as an alias for \objb \NewObjectCommand{\ref}{}{\obja} } \NewObject{\objc}{ \NewObjectCommand{\vara}{}{1} \NewObjectCommand{\varb}{}{10} \NewObjectCommand{\varc}{}{\fpeval{100 + \objc\vara + \objc\varb}} % TODO Allow to use \this as an alias for \objc }

\begin{document} \noindent document:\ \obja\var\ \objb\var\ \objb\func{marg}[oarg]\ \objb\ref\var\ xfp:\ \objc\varc\ \fpeval{100 + \objc\vara + \objc\varb}\ tikz/pgf:\ \pgfmathsetmacro{\varpgf}{100 + \objc\vara + \objc\varb}\varpgf\ % TODO error \tikz \draw (0, 0) -- (\objc\vara + 1, 0); % TODO error \end{document}

An object consists of object commands. Object commands can be used in other object commands of the same object or in object commands of other objects. For the first case I want to use \this as an alias for the current object. Is it possible to define a temporary command \this which gets directly expanded if used in the definition of object commands (without expanding other commands)? A similar expansion would be necessary if I want to use the temporary command \l__object_path_tl for \cs_to_str:N #1 which gets automatically expanded.

For the definition of objects and object commands I use non-expandable commands. As a result object commands cannot be used directly in TikZ/PGF commands. In xfp commands however non-expandable commands are possible. Is it possible to directly use non-expandable commands also in TikZ/PGF commands?

Related:

References:

Michael
  • 143
  • Interesting approach :) However, you will probably be limited by this macro language. As TeX was written in WEB, you may want to have a closer look there, e.g. via the [web] tag. // What is your current use case once you have objects available? – MS-SPO Aug 23 '21 at 16:08
  • @MS-SPO At the moment I have two use cases. The first use case is TikZ. I can pass different objects (same object commands but different values) to a command containing TikZ code and can access the variables and functions to create the drawing. The second use case is a mathematic variable object containing the different parts of a variable $\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:30
  • Interesting. Have you seen this contribution? https://tex.stackexchange.com/questions/423851/any-way-to-bundle-some-variables-in-an-oop-like-object-in-latex?noredirect=1&lq=1 – MS-SPO Aug 23 '21 at 16:46
  • 2
    I've had a couple of goes with oop in LaTeX. My latest thinking is to pretend I'm doing oop but actually work a bit more functionally. So the object ends up being a simple macro, and the methods are actually functions that get passed that macro as their first argument. So the this is 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
  • See https://stackoverflow.com/a/2973683/315213 – Andrew Stacey Aug 23 '21 at 18:00
  • The 2nd and 3rd arguments of \NewObjectCommand are actually not used at all by \NewObjectCommand. They are just passed to \NewDocumentCommandName. Thus you can define \NewObjectCommand as a single-argument-macro just doing \NewDocumentCommandName{\l__object_path_tl \cs_to_str:N #1}. \NewDocumentCommandName will "grab" subsequent arguments anyway. :-) – Ulrich Diez Aug 24 '21 at 16:01
  • How about \NewObject replacing in its 2nd argument each instance of the token \this by #1 before processing #2? – Ulrich Diez Aug 24 '21 at 16:03
  • @UlrichDiez Couldn't each object function start with \let\this=#1? – Andrew Stacey Aug 24 '21 at 17:10
  • @AndrewStacey \let is 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 \pgfsetmath evaluates 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--quantity, ... – Ulrich Diez Aug 24 '21 at 18:16
  • @AndrewStacey So the answer is: Yes, each function could. But then there would be many situations where each function could not be used. :-) – Ulrich Diez Aug 24 '21 at 18:18
  • Ah, I see. The complexity comes from the expandability requirement. That's beyond my ken. (Personally, I'd avoid that requirement. I'd do the work beforehand and store the results in some macros that I'd then use in the TIkZ commands.) – Andrew Stacey Aug 24 '21 at 18:39
  • @AndrewStacey Expandability-requirement is just about the question in which stage of TeX's processing of input you need the result. :-) If a macro is heavily used it might also be about memory: Expandability implies no temporary assignments while carrying out the macro, thus no additional permanent memory-consumption.In case of interest: I elaborated on stages of processing in my answer to Conversion of space characters into space tokens and in my answer to Define commands without the need for brackets – Ulrich Diez Aug 24 '21 at 19:15
  • @MS-SPO Yes, I had a look into the pgf module oo, but as far I tried e.g. \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

2 Answers2

3

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 &lt;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 &lt;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}

enter image description here

Ulrich Diez
  • 28,770
0

I took up the idea from @Ulrich Diez in his answer to replace \this in the code of the object definition #2 with the command for the object itself #1 using l3regex described in the interface3 manual.

For the use of object commands with tikz/pgf and siunitx (I added it as an additional use case) I used expandable document commands like @Ulrich Diez did and accepted the corresponding restrictions described in the xparse manual.

\documentclass{article}
%
\usepackage{expl3}
\usepackage{xfp}
\usepackage{tikz}
\usepackage{siunitx}
%
\ExplSyntaxOn
\NewDocumentCommand\NewExpandableDocumentCommandName{}{
    \exp_args:Nc \NewExpandableDocumentCommand
}
\tl_new:N \l__object_path_tl
\NewDocumentCommand\NewObject{m m}{
    \tl_set:Nn \l__object_path_tl {\cs_to_str:N #1}
% Use expandable document command as needed for use with tikz/pgf and siunitx
% TODO Use \l__object_path_tl for \cs_to_str:N #1 (must be expandend therefor)
\NewExpandableDocumentCommand#1{m}{\use:c {\cs_to_str:N #1 \cs_to_str:N ##1}}

% Replace \this used in #2 by #1 (see interface3 manual)
\tl_set:Nn \l__object_command_tl {#1}
\tl_set:Nn \l__object_code_tl {#2}
\regex_replace_all:nnN {\c{this}} {\u{\l__object_command_tl}} \l__object_code_tl
\l__object_code_tl

} \NewDocumentCommand\NewObjectCommand{m m m}{ % Use expandable document command as needed for use with tikz/pgf and siunitx % TODO Remove arguments #2 and #3 as not needed because they are subsequent arguments % that get processed by \NewExpandableDocumentCommandName anyway \NewExpandableDocumentCommandName{\l__object_path_tl \cs_to_str:N #1}{#2}{#3} } \ExplSyntaxOff % \NewObject{\obja}{% \NewObjectCommand{\var}{}{var obj~A}% }% \NewObject{\objb}{% \NewObjectCommand{\var}{}{var obj~B}% % Ensure that last argument is a mandatory argument because object commands are % expandable document commands (see restrictions described in xparse manual) \NewObjectCommand{\func}{O{} m}{func obj~B with #1, #2, \obja\var\ and \this\var}% \NewObjectCommand{\ref}{}{\obja}% }% \NewObject{\objc}{% \NewObjectCommand{\vara}{}{1}% \NewObjectCommand{\varb}{}{10}% \NewObjectCommand{\varc}{}{\fpeval{100 + \this\vara + \this\varb}}% \NewObjectCommand{\unit}{}{\milli\meter}% }% % \begin{document} \noindent document:\ \obja\var\ \objb\var\ \objb\func[oarg]{marg}\ \objb\ref\var\ xfp:\ \objc\varc\ \fpeval{100 + \objc\vara + \objc\varb}\ tikz/pgf:\ \pgfmathsetmacro{\varpgf}{100 + \objc\vara + \objc\varb}\varpgf\ \tikz \draw (0, 0) -- (\objc\vara + 1, 0);\ siunitx:\ \si{\objc\unit}% \end{document}

Now everything works so far, but I am not sure if the replacement of \this in #2 with #1 with l3regex is an "hacky" solution. I also tried to restrict the replacement to the code of each object command during its definition, but this seems to be not possible without expanding the temporary token list needed for the replacement.

Michael
  • 143