En expl3, puedo traducir una lista de tokens \l_greet_tl
en una secuencia de control \greet
que toma dos parámetros de la siguiente manera:
\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
Entonces, puedo escribir \greet { world } { favorite~species~(humans,~presumably) }
y recibir la siguiente expansión:
¡Hola Mundo! ¿Cómo le va a tu especie favorita (los humanos, presumiblemente)?
Sin embargo, ahora me gustaría hacer lo contrario, es decir, convertir el texto de reemplazo de la secuencia de control \greet
nuevamente en una lista de tokens, para poder agregarlo antes de volver a convertirlo en una secuencia de control. Puedo hacer esto manualmente para dos parámetros de la siguiente manera:
\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
Ahora puedo escribir \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
y recibir la siguiente expansión:
¡Hola Mundo! ¿Cómo le va a tu especie favorita (los humanos, presumiblemente)? ¡Que tengas un gran salto evolutivo!
Sin embargo, me gustaría poder hacer esto de una manera que funcione para cualquier número de parámetros, no solo dos, como por ejemplo:
\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
A diferencia de la función existente \cs_replacement_spec:N
, la nueva función \tl_set_from_cs:NNn
mantendría los códigos de categoría en el texto de reemplazo.
EDITAR: Más aclaraciones (2024-04-26)
A diferencia de la respuesta de Max Chernoff, la respuesta debería aplicarse a todos los motores TeX compatibles con expl3, no solo a LuaTeX.
A diferencia de las respuestas mías y de @egreg, #
se deben conservar las s duplicadas en el texto de reemplazo. Por ejemplo, supongamos la siguiente redefinición de la secuencia de control \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:
}
Ahora puedo escribir \greet { world } { favorite~species~(humans,~presumably) }
y seguir recibiendo la siguiente expansión:
¡Hola Mundo! ¿Cómo le va a tu especie favorita (los humanos, presumiblemente)?
Sin embargo, convertir la secuencia de control en una lista de tokens de la siguiente manera ya no se comporta como se esperaba:
\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
Esto se debe a que \greet
ahora contiene el siguiente texto de reemplazo:
Hello,~#1!
\group_begin:
\cs_set:Npn
\greet
#1
{ ~How~is~#1~doing? }
\greet { #2 }
\group_end:
~Have~a~great~#3!
Como puede ver, la distinción entre #1
y ##1
se ha perdido. Si ahora escribo \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
, TeX producirá el siguiente error:
El uso de \greet no coincide con su definición.
Preservar la #
s duplicada es parte del desafío.
Respuesta1
Para macros con argumentos no delimitados:
\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
La consola imprimiría
> \l_tmpa_tl=-##1-##2-.
<recently read> }
l.36 \tl_show:N \l_tmpa_tl
?
EQUAL
Almaceno los posibles textos de parámetros en una secuencia, de la que extraigo el elemento adecuado para pasarlo cuando \tl_set:No
se realiza.
Respuesta2
También es posible hacer esto para macros con argumentos delimitados, si puedes usar LuaTeX. Sin embargo, obtener los códigos cat de una definición de macro es mucho más difícil de lo que cabría esperar, por lo que necesitamos usar muchos trucos sucios para que esto funcione.
\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}
Hay dos “trucos” principales para que esto funcione.
Para que esto funcione en general, necesitamos la definición de la macro como una lista de tokens (con códigos cat adecuados), no solo como una cadena Lua. Entonces, para obtener la definición de una macro, primero creamos un
\mark
nodo vacío. A continuación, utilizamos algunos trucos para establecer su contenido como un número entero que representa la definición de macro. Luego, cuando preguntamos por el valor del\mark
nodo, obtenemos el contenido de la macro, representado como una tabla Lua.Luego, para configurar un
\toks
registro con los tokens de una tabla Lua, utilizamos el mismo\mark
truco, pero a la inversa. Luego, creamos un\chardef
token que apunta a la misma lista de tokens a la que\mark
apunta. A continuación, desbordamos con cuidado el índice de un\toks
registro para que acabe apuntando al\chardef
token. Debido a que seleccionamos cuidadosamente las compensaciones, este\toks
registro "falso" ahora actúa como si tuviera el mismo valor que el archivo\mark
.
También necesitamos algo de código para convertir los códigos de token TeX internos nuevamente a la forma que espera la interfaz y algo de código para mover el \toks
registro falso a uno real, pero esto es relativamente sencillo.
Respuesta3
Aquí hay una función \tl_set_from_cs:NNn
que toma una lista de tokens, una secuencia de control y una cantidad de parámetros, y asigna el texto de reemplazo de la secuencia de control a la lista de tokens:
\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
}
A diferencia de la respuesta de egreg, no se requiere ninguna estructura de datos adicional. Esto tiene el costo de pasos de expansión adicionales, por lo que el enfoque de egreg puede ser preferible si la función se va a llamar con frecuencia.
Aquí hay una demostración del uso de la función \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
Ahora puedo escribir \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
y recibir la siguiente expansión:
¡Hola Mundo! ¿Cómo le va a tu especie favorita (los humanos, presumiblemente)? ¡Que tengas un gran salto evolutivo!
Como se demuestra en la respuesta de egreg, también podemos simplificar la firma de la función \tl_set_from_cs:NN
deduciendo el número de parámetros de la especificación del parámetro:
\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 }
Aquí hay una demostración del uso de la función \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
Ahora puedo escribir \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
y recibir la siguiente expansión nuevamente:
¡Hola Mundo! ¿Cómo le va a tu especie favorita (los humanos, presumiblemente)? ¡Que tengas un gran salto evolutivo!
Si la función \tl_set_from_cs:NN
fuera a agregarse al módulo l3tl de expl3, también se deberían agregar otras variantes, como \tl_gset_from_cs:NN
.
Además, debe quedar claramente documentado que de esta manera sólo las secuencias de control con parámetros no delimitados pueden convertirse en listas de tokens.
EDITAR: Más aclaraciones (2024-04-27)
Podemos conservar #
la s duplicada en el texto de reemplazo de la siguiente manera:
- Amplíe la secuencia de control utilizando como parámetros secuencias de control como
\witiko_parameter_1
,\witiko_parameter_2
,\witiko_parameter_3
, etc. que es poco probable que aparezcan en el texto de reemplazo. - Duplica la
#
s en el resultado de la expansión. - Reemplace
\witiko_parameter_1
,\witiko_parameter_2
,\witiko_parameter_3
, etc. en el resultado de la expansión con#1
,#2
,#3
, etc.
Aquí hay una implementación concreta:
\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
}
Aquí hay una demostración de la implementación:
\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
Ahora puedo escribir \greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }
y recibir la siguiente expansión:
¡Hola Mundo! ¿Cómo le va a tu especie favorita (los humanos, presumiblemente)? ¡Que tengas un gran salto evolutivo!
Esto es lo esperado.
Respuesta4
Podemos hacer que esto funcione con caracteres de parámetro duplicados ( #
), con argumentos delimitados y con cualquier motor, con dos restricciones:
La macro objetivono puedoutilizar
"FF
como carácter de parámetro macro.Cualquier delimitadordebetienen códigos catcode "normales".
Yo esperaría que >99% de las macros cumplieran estos requisitos, por lo que esto no debería ser demasiado restrictivo en la práctica.
\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}
El truco aquí consiste en utilizar un carácter de parámetro de macro diferente al expandir la macro de destino para que podamos distinguir entre los parámetros anidados "internos" y los parámetros "reales" más externos.