In expl3 kann ich eine Tokenliste \l_greet_tl
in eine Steuersequenz übersetzen \greet
, die zwei Parameter wie folgt annimmt:
\tl_new:N
\l_greet_tl
\tl_set:Nn
\l_greet_tl
{ Hello,~#1!~How~is~#2~doing? }
\cs_generate_variant:Nn
\cs_generate_from_arg_count:NNnn
{ NNnV }
\cs_generate_from_arg_count:NNnV
\greet
\cs_set:Npn
{ 2 }
\l_greet_tl
Dann kann ich \greet { world } { favorite~species~(humans,~presumably) }
die folgende Erweiterung schreiben und erhalten:
Hallo Welt! Wie geht es deiner Lieblingsspezies (vermutlich dem Menschen)?
Ich möchte nun allerdings den umgekehrten Weg gehen, also den Ersetzungstext der Kontrollsequenz \greet
wieder in eine Tokenliste umwandeln, um ihn dann vor der Rückverwandlung in eine Kontrollsequenz noch anhängen zu können. Manuell kann ich das für zwei Parameter folgendermaßen machen:
\tl_set:No
\l_greet_tl
{ \greet { #1 } { #2 } }
\tl_put_right:Nn
\l_greet_tl
{ ~Have~a~great~#3! }
\cs_generate_from_arg_count:NNnV
\greet
\cs_set:Npn
{ 3 }
\l_greet_tl
Jetzt kann ich \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
die folgende Erweiterung schreiben und empfangen:
Hallo Welt! Wie geht es deiner Lieblingsspezies (vermutlich dem Menschen)? Mach einen großen Evolutionssprung!
Ich möchte dies jedoch auf eine Weise tun können, die für eine beliebige Anzahl von Parametern funktioniert, nicht nur für zwei, wie zum Beispiel:
\tl_set_from_cs:NNn
\l_greet_tl % token list (output)
\greet % control sequence (input)
{ 2 } % parameter count
\tl_put_right:Nn
\l_greet_tl
{ ~Have~a~great~#3! }
\cs_generate_from_arg_count:NNnV
\greet
\cs_set:Npn
{ 3 }
\l_greet_tl
Im Gegensatz zur vorhandenen Funktion würde \cs_replacement_spec:N
die neue Funktion \tl_set_from_cs:NNn
die Kategoriencodes im Ersetzungstext beibehalten.
EDIT: Weitere Klarstellungen (26.04.2024)
Anders als in der Antwort von Max Chernoff sollte die Antwort für alle von expl3 unterstützten TeX-Engines gelten, nicht nur für LuaTeX.
Anders als in den Antworten von mir und @egreg #
sollten doppelte s im Ersetzungstext erhalten bleiben. Nehmen wir beispielsweise die folgende Neudefinition der Steuersequenz an \greet
:
\cs_new_protected:Npn
\greet
#1#2
{
Hello,~#1!
\group_begin:
\cs_set:Npn
\greet
##1
{ ~How~is~##1~doing? }
\greet { #2 }
\group_end:
}
Nun kann ich schreiben \greet { world } { favorite~species~(humans,~presumably) }
und erhalte trotzdem folgende Erweiterung:
Hallo Welt! Wie geht es deiner Lieblingsspezies (vermutlich dem Menschen)?
Das Konvertieren der Steuersequenz in eine Token-Liste wie folgt verhält sich jedoch nicht mehr wie erwartet:
\tl_set:No
\l_greet_tl
{ \greet { #1 } { #2 } }
\tl_put_right:Nn
\l_greet_tl
{ ~Have~a~great~#3! }
\cs_generate_from_arg_count:NNnV
\greet
\cs_set:Npn
{ 3 }
\l_greet_tl
Dies liegt daran, dass \greet
jetzt der folgende Ersatztext enthalten ist:
Hello,~#1!
\group_begin:
\cs_set:Npn
\greet
#1
{ ~How~is~#1~doing? }
\greet { #2 }
\group_end:
~Have~a~great~#3!
Wie Sie sehen, ist die Unterscheidung zwischen #1
und ##1
verloren gegangen. Wenn ich jetzt schreibe \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
, erzeugt TeX den folgenden Fehler:
Die Verwendung von \greet entspricht nicht der Definition.
Das Beibehalten des doppelten #
s ist Teil der Herausforderung.
Antwort1
Für Makros mit nicht begrenzten Argumenten:
\documentclass{article}
\newcommand{\test}[2]{-#1-#2-}
\ExplSyntaxOn
\seq_new:N \l__witiko_getreplacement_seq
\tl_new:N \l__witiko_getreplacement_tmp_tl
\seq_set_from_clist:Nn \l__witiko_getreplacement_seq
{
{},{#1},{},{#1}{#2},{},{#1}{#2}{#3},{},{#1}{#2}{#3}{#4},
{},{#1}{#2}{#3}{#4}{#5},{},{#1}{#2}{#3}{#4}{#5}{#6},
{},{#1}{#2}{#3}{#4}{#5}{#6}{#7},
{}.{#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8},
{},{#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}{#9}
}
\cs_new_protected:Nn \witiko_getreplacement:NN
{
\tl_set:Ne \l__witiko_getreplacement_tmp_tl
{
\seq_item:Nn \l__witiko_getreplacement_seq { \tl_count:e { \cs_parameter_spec:N #1 } }
}
\__witiko_getreplacement:NNV #2 #1 \l__witiko_getreplacement_tmp_tl
}
\cs_new_protected:Nn \__witiko_getreplacement:NNn
{
\tl_set:No #1 { #2 #3 }
}
\cs_generate_variant:Nn \__witiko_getreplacement:NNn { NNV }
\witiko_getreplacement:NN \test \l_tmpa_tl
\tl_show:N \l_tmpa_tl
\tl_set:Nn \l_tmpb_tl { -#1-#2- }
\tl_if_eq:NNTF \l_tmpa_tl \l_tmpb_tl { \typeout{EQUAL} } { \typeout{DIFFERENT} }
\stop
Die Konsole druckt
> \l_tmpa_tl=-##1-##2-.
<recently read> }
l.36 \tl_show:N \l_tmpa_tl
?
EQUAL
Die möglichen Parametertexte speichere ich in einer Sequenz, aus der ich bei der \tl_set:No
Ausführung jeweils das passende Item extrahiere um es zu übergeben.
Antwort2
Dies ist auch für Makros mit abgegrenzten Argumenten möglich, wenn Sie LuaTeX verwenden können. Die Catcodes einer Makrodefinition zu erhalten ist jedoch viel schwieriger als erwartet, daher müssen wir viele schmutzige Tricks anwenden, damit dies funktioniert.
\documentclass{article}
\usepackage{luacode}
\begin{luacode*}
-----------------
--- Constants ---
-----------------
local assign_toks_cmdcode = token.command_id("assign_toks")
local car_ret_cmdcode = token.command_id("car_ret")
local cs_token_flag = 0x1FFFFFFF
local first_digit_chrcode = string.byte("0")
local first_letter_chrcode = string.byte("a")
local hash_chrcode = string.byte("#")
local last_letter_chrcode = string.byte("z")
local let_token = token.create("let")
local mac_param_cmdcode = token.command_id("mac_param")
local other_char_cmdcode = token.command_id("other_char")
local par_end_cmdcode = token.command_id("par_end")
local random_csname_length = 8
local slice = table.sub
local stop_cmdcode = token.command_id("stop")
local tokspre_token = token.create("tokspre")
----------------------------
--- Function Definitions ---
----------------------------
-- Gets a table representing tokens of a macro definition
function get_toktab_from_macro(value_csname)
local value_token = token.create(value_csname)
-- By default, LuaTeX only gives us the contents of a macro as a string.
-- However, it will give us the contents of a mark node as a table of
-- tokens, so we create a fake mark node that points to the macro's
-- definition.
local tmp_nd = node.direct.new("mark")
node.direct.setprev(tmp_nd + 1, value_token.mode)
return node.direct.getfield(tmp_nd, "mark")
end
-- Splits a macro definition token table into its parameters and its
-- replacement text.
local function split_macro_toktab(meaning_toktab)
local stop_index
local args_count = 0
for i, t in ipairs(meaning_toktab) do
-- Separator between parameters and replacement text (represented by
-- "->" inside of \meaning).
if t[1] == stop_cmdcode then
stop_index = i
-- Convert a macro parameter token in the body back into a "#"
-- token.
elseif t[1] == mac_param_cmdcode and t[3] == 0 then
table.insert(
meaning_toktab,
i + 1,
{ mac_param_cmdcode, hash_chrcode, 1 }
)
elseif t[1] == mac_param_cmdcode and t[3] == 1 then
t[3] = 0
-- Convert a macro parameter token in the body back into a <digit>
-- token.
elseif t[1] == car_ret_cmdcode then
table.insert(
meaning_toktab,
i + 1,
{ other_char_cmdcode, first_digit_chrcode + t[2], 0 }
)
t[2] = hash_chrcode
t[1] = mac_param_cmdcode
-- Convert a macro parameter token in the parameters back into a
-- pair of tokens {"#", <digit>}.
elseif t[1] == par_end_cmdcode then
args_count = args_count + 1
t[1] = mac_param_cmdcode
t[2] = hash_chrcode
table.insert(
meaning_toktab,
i + 1,
{ other_char_cmdcode, first_digit_chrcode + args_count, 0 }
)
end
end
-- Split the token table
return slice(meaning_toktab, 2, stop_index - 1),
slice(meaning_toktab, stop_index + 1, nil )
end
-- Generates a random control sequence name.
local function random_csname()
local random_table = {}
for i = 1, random_csname_length do
local random_letter = string.char(
math.random(first_letter_chrcode, last_letter_chrcode)
)
table.insert(random_table, random_letter)
end
return table.concat(random_table)
end
-- Converts a token table into a \toks token (without giving it a name).
local function toktab_to_token(value_toktab)
local tmp_csname = random_csname()
-- First, we create a mark node to store the raw token in.
local tmp_nd = node.direct.new("mark")
node.direct.setfield(tmp_nd, "mark", value_toktab)
-- TeX expects two levels of indirection for a \toks token, so we first
-- point a \chardef token to the token created by the mark node.
token.set_char(tmp_csname, node.direct.getprev(tmp_nd + 1), "global")
-- Then, we create a \toks token that points to the \chardef token.
return token.create(
token.create(tmp_csname).tok - cs_token_flag,
assign_toks_cmdcode
)
end
-- \let's a token to a control sequence name.
local function let_csname_token(name_csname, value_token)
-- We need to create a token with the name first, otherwise we get an
-- "undefined_cs" token which is useless.
token.set_char(name_csname, 0)
local name_token = token.create(name_csname)
-- There's no way to do this directly from Lua, so we start a new TeX
-- run and use \let to assign the token.
tex.runtoks(function()
token.put_next(let_token, name_token, value_token)
end)
return token.create(name_csname)
end
-- Copies a fake \toks token into a real \toks register.
--
-- The token created by "let_csname_token" is a semi-valid \toks register:
-- it behaves like a \toks register with \the and similar, but it gives a
-- (mostly harmless) error with \show and \meaning. To fix this, we copy
-- the token's contents into a real \toks register.
local function token_to_toksreg(toksreg_csname, value_token)
-- Clear the register
tex.toks[toksreg_csname] = ""
local toksreg_token = token.create(toksreg_csname)
local value_toksreg = let_csname_token(random_csname(), value_token)
-- Prepend the fake \toks register onto the empty real one, giving
-- us a real \toks register with the correct value.
tex.runtoks(function()
token.put_next(tokspre_token, toksreg_token, value_toksreg)
end)
end
-- Registers a TeX command that calls the given Lua function.
local function register_tex_cmd(target_csname, func, args)
local scanners = {}
for _, arg in ipairs(args) do
scanners[#scanners+1] = token["scan_" .. arg]
end
local function scanning_func()
local values = {}
for _, scanner in ipairs(scanners) do
values[#values+1] = scanner()
end
func(table.unpack(values))
end
local index = luatexbase.new_luafunction(target_csname)
lua.get_functions_table()[index] = scanning_func
token.set_lua(target_csname, index, "global")
end
--------------------
--- TeX Commands ---
--------------------
register_tex_cmd("__example_macro_to_toks:N", function(value_csname)
-- Get a table representing the macro's definition tokens
local meaning_toktab = get_toktab_from_macro(value_csname)
-- Split the macro into its parameters and replacement text
local params_toktab, replacement_toktab = split_macro_toktab(meaning_toktab)
-- Save the parameters in a \toks register
local params_token = toktab_to_token(params_toktab)
token_to_toksreg("l__example_parameters_toks", params_token)
-- Save the replacement text in a \toks register
local replacement_token = toktab_to_token(replacement_toktab)
token_to_toksreg("l__example_replacement_toks", replacement_token)
end, {"csname"})
\end{luacode*}
\ExplSyntaxOn
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Variable Declarations %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\exp_args_generate:n { NNNV }
\newtoks \l__example_parameters_toks
\newtoks \l__example_replacement_toks
\tl_new:N \l_example_parameters_tl
\tl_new:N \l_example_replacement_tl
\scan_new:N \s_example
%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Macro Definitions %%%
%%%%%%%%%%%%%%%%%%%%%%%%%
% Sets "\l_example_parameters_tl" with the parameters of the provided macro
% and "\l_example_replacement_tl" with the replacement text of the same
% macro.
\cs_new:Nn \example_macro_to_tl:N {
\__example_macro_to_toks:N #1
\tl_set:NV \l_example_parameters_tl \l__example_parameters_toks
\tl_set:NV \l_example_replacement_tl \l__example_replacement_toks
}
% Defines the provided macro with parameters "\l_example_parameters_tl" and
% replacement text "\l_example_replacement_tl".
\cs_new:Nn \example_tl_to_macro:N {
\exp_args:NNNNV \exp_last_unbraced:NNV \cs_set:Npn #1 \l_example_parameters_tl \l_example_replacement_tl
}
\ExplSyntaxOff
%%%%%%%%%%%%%%%%%%%%%
%%% Demonstration %%%
%%%%%%%%%%%%%%%%%%%%%
\pagestyle{empty}
\begin{document}
\ExplSyntaxOn
% Define a test macro with weird parameters and body
\cs_new:Npn \example_test:w #1 #2 \s_example {
A B C~
<#1>
{ #2 }
\par $a^2 + b^\bgroup \pi^2\egroup$
}
% Get the parameters and replacement text of the test macro
\example_macro_to_tl:N \example_test:w
% Inspect the extracted token lists
\tl_analysis_show:N \l_example_parameters_tl
\tl_analysis_show:N \l_example_replacement_tl
% Modify the extracted token lists
\tl_put_right:Nn \l_example_parameters_tl { #3 }
\tl_put_left:Nn \l_example_replacement_tl { \textbf{#3} }
% Assemble a new macro from the modified token lists
\example_tl_to_macro:N \test
% Test the new macro
\test X {\itshape y}\s_example Z
% Compare the meanings of the original and new macros
\par \meaning\example_test:w
\par \meaning\test
\ExplSyntaxOff
\end{document}
Damit dies funktioniert, sind zwei wesentliche „Tricks“ nötig.
Damit dies im Allgemeinen funktioniert, benötigen wir die Definition des Makros als Token-Liste (mit den richtigen Catcodes), nicht nur als Lua-String. Um die Definition eines Makros zu erhalten, erstellen wir also zuerst einen leeren
\mark
Knoten. Als Nächstes verwenden wir einen Trick, um seinen Inhalt als Ganzzahl festzulegen, die die Makrodefinition darstellt. Wenn wir dann nach dem Wert des\mark
Knotens fragen, erhalten wir tatsächlich den Inhalt des Makros, dargestellt als Lua-Tabelle.Um dann ein Register mit den Token aus einer Lua-Tabelle zu setzen
\toks
, verwenden wir denselben\mark
Trick, nur umgekehrt. Dann erstellen wir ein\chardef
Token, das auf dieselbe Tokenliste zeigt wie das\mark
. Als nächstes überlaufen wir vorsichtig den Index eines\toks
Registers, sodass er am Ende auf das\chardef
Token zeigt. Da wir die Offsets sorgfältig ausgewählt haben, verhält sich dieses „falsche“\toks
Register jetzt so, als hätte es denselben Wert wie das\mark
.
Wir benötigen außerdem etwas Code, um die internen TeX-Token-Codes wieder in die vom Frontend erwartete Form zu konvertieren und etwas Code, um das gefälschte \toks
Register in ein echtes zu verwandeln, aber das ist relativ unkompliziert.
Antwort3
Hier ist eine Funktion \tl_set_from_cs:NNn
, die eine Tokenliste, eine Steuersequenz und eine Anzahl von Parametern aufnimmt und den Ersetzungstext der Steuersequenz der Tokenliste zuweist:
\cs_new_protected:Nn
\tl_set_from_cs:NNn
{
\tl_set:Nn
\l_tmpa_tl
{ #2 }
\int_step_inline:nn
{ #3 }
{
\tl_put_right:Nn
\l_tmpa_tl
{ { #### ##1 } }
}
\exp_args:NNV
\tl_set:No
#1
\l_tmpa_tl
}
Anders als in der Antwort von egreg ist keine zusätzliche Datenstruktur erforderlich. Dies geht auf Kosten zusätzlicher Erweiterungsschritte, daher ist der Ansatz von egreg möglicherweise vorzuziehen, wenn die Funktion häufig aufgerufen werden soll.
Hier ist eine Demonstration der Verwendung der Funktion \tl_set_from_cs:NNn
:
\cs_new:Npn
\greet
#1#2
{ Hello,~#1!~How~is~#2~doing? }
\tl_set_from_cs:NNn
\l_greet_tl
\greet
{ 2 }
\tl_put_right:Nn
\l_greet_tl
{ ~Have~a~great~#3! }
\cs_generate_variant:Nn
\cs_generate_from_arg_count:NNnn
{ NNnV }
\cs_generate_from_arg_count:NNnV
\greet
\cs_set:Npn
{ 3 }
\l_greet_tl
Jetzt kann ich \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
die folgende Erweiterung schreiben und empfangen:
Hallo Welt! Wie geht es deiner Lieblingsspezies (vermutlich dem Menschen)? Mach einen großen Evolutionssprung!
Wie in der Antwort von egreg gezeigt, können wir die Funktionssignatur auch vereinfachen, indem wir \tl_set_from_cs:NN
die Anzahl der Parameter aus der Parameterspezifikation ableiten:
\cs_new_protected:Nn
\tl_set_from_cs:NN
{
\tl_set:Ne
\l_tmpa_tl
{ \cs_parameter_spec:N #2 }
\int_set:Nn
\l_tmpa_int
{ \tl_count:N \l_tmpa_tl / 2 }
\tl_set_from_cs:NNV
#1
#2
\l_tmpa_int
}
\cs_generate_variant:Nn
\tl_set_from_cs:NNn
{ NNV }
Hier ist eine Demonstration der Verwendung der Funktion \tl_set_from_cs:NN
:
\cs_new:Npn
\greet
#1#2
{ Hello,~#1!~How~is~#2~doing? }
\tl_set_from_cs:NN
\l_greet_tl
\greet
\tl_put_right:Nn
\l_greet_tl
{ ~Have~a~great~#3! }
\cs_generate_from_arg_count:NNnV
\greet
\cs_set:Npn
{ 3 }
\l_greet_tl
Jetzt kann ich \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
wieder folgende Erweiterung schreiben und empfangen:
Hallo Welt! Wie geht es deiner Lieblingsspezies (vermutlich dem Menschen)? Mach einen großen Evolutionssprung!
Wenn die Funktion \tl_set_from_cs:NN
zum l3tl-Modul von expl3 hinzugefügt werden sollte, müssten auch andere Varianten hinzugefügt werden, wie z. B. \tl_gset_from_cs:NN
.
Darüber hinaus sollte klar dokumentiert werden, dass nur Steuersequenzen mit nicht begrenzten Parametern auf diese Weise in Tokenlisten umgewandelt werden können.
EDIT: Weitere Klarstellungen (27.04.2024)
Wir können doppelte #
s im Ersetzungstext wie folgt beibehalten:
- Erweitern Sie die Steuersequenz, indem Sie als Parameter Steuersequenzen wie
\witiko_parameter_1
,\witiko_parameter_2
,\witiko_parameter_3
, usw. verwenden, die im Ersetzungstext wahrscheinlich nicht vorkommen. - Verdoppeln Sie das
#
s im Ergebnis der Erweiterung. - Ersetzen Sie
\witiko_parameter_1
,\witiko_parameter_2
,\witiko_parameter_3
, usw. im Ergebnis der Erweiterung durch#1
,#2
,#3
, usw.
Hier ist eine konkrete Implementierung:
\cs_new_protected:Nn
\tl_set_from_cs:NNn
{
\tl_set:Nn
\l_tmpa_tl
{ #2 }
\int_step_inline:nn
{ #3 }
{
\exp_args:NNc
\tl_put_right:Nn
\l_tmpa_tl
{ witiko_parameter_ ##1 }
}
\exp_args:NNV
\tl_set:No
\l_tmpb_tl
\l_tmpa_tl
\regex_replace_all:nnN
{ \cP. }
{ \0\0 }
\l_tmpb_tl
\int_step_inline:nn
{ #3 }
{
\regex_replace_all:nnN
{ \c { witiko_parameter_ ##1 } }
{ \cP\# ##1 }
\l_tmpb_tl
}
\tl_set:NV
#1
\l_tmpb_tl
}
Hier ist eine Demonstration der Implementierung:
\cs_new_protected:Npn
\greet
#1#2
{
Hello,~#1!
\group_begin:
\cs_set:Npn
\greet
##1
{ ~How~is~##1~doing? }
\greet { #2 }
\group_end:
}
\tl_set_from_cs:NN
\l_greet_tl
\greet
\tl_put_right:Nn
\l_greet_tl
{ ~Have~a~great~#3! }
\cs_generate_from_arg_count:NNnV
\greet
\cs_set:Npn
{ 3 }
\l_greet_tl
Jetzt kann ich \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
die folgende Erweiterung schreiben und erhalten:
Hallo Welt! Wie geht es deiner Lieblingsspezies (vermutlich dem Menschen)? Mach einen großen Evolutionssprung!
Das ist wie erwartet.
Antwort4
Dies gelingt uns mit doppelten Parameterzeichen ( #
), mit abgegrenzten Argumenten und mit jeder Engine, allerdings mit zwei Einschränkungen:
Das Zielmakrokann nicht
"FF
als Makroparameter Zeichen verwenden .Alle Trennzeichenmusshaben „normale“ Catcodes.
Ich würde erwarten, dass >99 % der Makros diese Anforderungen erfüllen, daher sollte dies in der Praxis nicht zu restriktiv sein.
\documentclass{article}
\pagestyle{empty}
\parindent=0pt
\ExplSyntaxOn
%%%%%%%%%%%%%%%%%%%
%%% Definitions %%%
%%%%%%%%%%%%%%%%%%%
%% Copies the replacement text of a macro (#1) into a token list (#2).
\cs_new_protected:Nn \example_cs_to_tl:NN {
%% Get the parameters used by the macro
\tl_set:Ne #2 { \cs_parameter_spec:N #1 }
%% Convert the parameters into normal catcodes to handle any delimited
%% arguments.
\tl_set_rescan:NnV #2 {} #2
%% Use <"FF>_6 as the macro parameter character instead of the usual <#>_6.
%% We do this so that we can distinguish between any inner macro parameters
%% and the new ones that we're passing in here.
\regex_replace_all:nnN { \# ( \d ) } { { \cP\xFF \1 } } #2
%% Expand the macro to get at its replacement text.
\tl_set:Nf #2 {
\exp_last_unbraced:NNNo \exp_after:wN \exp_stop_f: #1 #2
}
%% Double all the original parameter characters, ignoring our new ones.
\regex_replace_all:nnN { \cP [^ \xFF ] } { \0 \0 } #2
}
%% I've used inline regexes here to make the code easier to follow, but you
%% should use `\regex_const:Nn` in the real code since it's considerably faster.
\cs_generate_variant:Nn \cs_generate_from_arg_count:NNnn { NNnV }
%%%%%%%%%%%%%%%%%%%%%
%%% Demonstration %%%
%%%%%%%%%%%%%%%%%%%%%
%% A test macro with two normal arguments and some inner doubled #'s.
\cs_new:Npn \original #1 #2 {
Hello,~#1!~
\group_begin:
\cs_set:Npn \original ##1 {
~How~is~##1~doing?
\cs_gset:Npn \inner ####1 {
#1~##1~####1.
}
}
\original { #2 }
\group_end:
}
%% Extract the replacement text and define a new macro with the same body.
\example_cs_to_tl:NN \original \l_tmpa_tl
\cs_generate_from_arg_count:NNnV \new \cs_set:Npn { 2 } \l_tmpa_tl
%% A test macro with some weird delimited arguments.
\cs_new:Npn \weirddelims #1, #2 #3 ABC \relax {
<#2>
\def \inner ##1 #3 {
<#1>
}
}
%% Extract the replacement text and define a new macro with the same body.
\example_cs_to_tl:NN \weirddelims \l_tmpa_tl
\cs_generate_from_arg_count:NNnV \newweirddelims \cs_set:Npn { 3 } \l_tmpa_tl
\ExplSyntaxOff
%% Show the comparison between the original and new macros.
\begin{document}
\texttt{\small \meaning\original}%
\par
\original{AAA}{BBB} \inner{CCC}
\bigskip
\texttt{\small \meaning\original}%
\par
\new{DDD}{EEE} \inner{FFF}
\bigskip
\texttt{\small \meaning\weirddelims}%
\par
\weirddelims{GGG},{HHH}{III}ABC\relax \inner{JJJ}III
\bigskip
\texttt{\small \meaning\newweirddelims}%
\par
\newweirddelims{KKK}{LLL}{MMM} \inner{NNN}MMM
\end{document}
Der Trick besteht hier darin, beim Erweitern des Zielmakros ein anderes Makroparameterzeichen zu verwenden, sodass wir zwischen den „inneren“ verschachtelten Parametern und den äußersten „echten“ Parametern unterscheiden können.