可変数の区切りのないパラメータを持つ制御シーケンスをトークンリストに変換する

可変数の区切りのないパラメータを持つ制御シーケンスをトークンリストに変換する

expl3 では、トークン リストを次のように 2 つのパラメーターを取る\l_greet_tl制御シーケンスに変換できます。\greet

\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

すると、\greet { world } { favorite~species~(humans,~presumably) }次の展開を記述して受け取ることができます。

こんにちは、世界!あなたの好きな種族(おそらく人間)はどうですか?

ただし、今度は逆のこと、つまり、制御シーケンスの置換テキストを\greetトークン リストに変換し直して、制御シーケンスに戻す前に追加できるようにしたいと思います。次のように、2 つのパラメーターに対して手動でこれを行うことができます。

\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

これで、\greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }次の展開を記述して受け取ることができます。

こんにちは、世界!あなたの好きな種族(おそらく人間)はどうですか?素晴らしい進化の飛躍を!

ただし、次のように、2 つだけではなく任意の数のパラメータで機能する方法でこれを実行できるようにしたいと思います。

\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

既存の機能とは異なり\cs_replacement_spec:N、新しい機能で\tl_set_from_cs:NNnは置換テキスト内のカテゴリ コードが維持されます。

編集: さらなる説明 (2024-04-26)

Max Chernoff の回答とは異なり、この回答は LuaTeX だけでなく、expl3 でサポートされているすべての TeX エンジンに適用されるはずです。

私や @egreg の回答とは異なり、#置換テキスト内の重複した s は保持される必要があります。たとえば、制御シーケンスの次の再定義を想定します\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:
  }

これで、次のように記述して\greet { world } { favorite~species~(humans,~presumably) }、次の展開を受け取ることができるようになりました。

こんにちは、世界!あなたの好きな種族(おそらく人間)はどうですか?

ただし、次のように制御シーケンスをトークン リストに変換すると、期待どおりに動作しなくなります。

\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

これは、\greet次の置換テキストが含まれるようになったためです。

Hello,~#1!
\group_begin:
\cs_set:Npn
  \greet
  #1
  { ~How~is~#1~doing? }
\greet { #2 }
\group_end:
~Have~a~great~#3!

ご覧のとおり、#1との区別##1が失われています。 ここで と書くと\greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }、TeX は次のエラーを生成します。

\greet の使用がその定義と一致しません。

二重の#s を維持することは課題の一部です。

答え1

区切りのない引数を持つマクロの場合:

\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

コンソールに印刷される

> \l_tmpa_tl=-##1-##2-.
<recently read> }

l.36 \tl_show:N \l_tmpa_tl

?
EQUAL

可能なパラメータ テキストをシーケンスに保存し、\tl_set:No実行時に適切な項目を抽出して渡します。

答え2

LuaTeX を使用できる場合は、区切りの引数を持つマクロに対してもこれを行うことができます。ただし、マクロ定義の catcode を取得するのは予想よりもはるかに難しいため、これを機能させるには多くの汚いトリックを使用する必要があります。

\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}

出力

これを機能させるには、主に 2 つの「コツ」があります。

  1. これを一般的に機能させるには、マクロの定義を Lua 文字列としてではなく、トークン リスト (適切な catcode 付き) として取得する必要があります。したがって、マクロの定義を取得するには、まず空の\markノードを作成します。次に、何らかのトリックを使用して、その内容をマクロ定義を表す整数として設定します。次に、ノードの値を要求すると\mark、実際には Lua テーブルとして表されるマクロの内容が取得されます。

  2. 次に、\toksLua テーブルからのトークンでレジスタを設定するために、同じ\markトリックを逆に使用します。次に、が指している\chardefのと同じトークン リストを指すトークンを作成します\mark。次に、レジスタのインデックスを慎重にオーバーフローさせて\toks、最終的に\chardefトークンを指すようにします。オフセットを慎重に選択したため、この「偽の」\toksレジスタは、 と同じ値を持っているかのように動作します\mark

また、内部の TeX トークン コードをフロントエンドが期待する形式に戻すコードと、偽の\toksレジスタを実際のレジスタに移動するコードも必要ですが、これは比較的簡単です。

答え3

\tl_set_from_cs:NNn以下は、トークン リスト、制御シーケンス、およびいくつかのパラメーターを受け取り、制御シーケンスの置換テキストをトークン リストに割り当てる関数です。

\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
  }

egreg の回答とは異なり、追加のデータ構造は必要ありません。ただし、追加の拡張手順が必要になるため、関数が頻繁に呼び出される場合は egreg のアプローチの方が適している可能性があります。

関数の使用例を以下に示します\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

これで、\greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }次の展開を記述して受け取ることができます。

こんにちは、世界!あなたの好きな種族(おそらく人間)はどうですか?素晴らしい進化の飛躍を!

\tl_set_from_cs:NNegreg の回答で示されているように、パラメータ仕様からパラメータの数を推測することで、関数シグネチャを次のように簡略化することもできます。

\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 }

関数の使用例を以下に示します\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

これで、\greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }次の展開を再度記述して受け取ることができます。

こんにちは、世界!あなたの好きな種族(おそらく人間)はどうですか?素晴らしい進化の飛躍を!

この関数を\tl_set_from_cs:NNexpl3 の l3tl モジュールに追加する場合は、 などの他のバリアントも追加する必要があります\tl_gset_from_cs:NN

さらに、区切られていないパラメータを持つ制御シーケンスのみがこの方法でトークン リストに変換できることを明確に文書化する必要があります。

編集: さらなる説明 (2024-04-27)

#次のようにして、置換テキスト内の重複した s を保持できます。

  1. 置換テキストに出現する可能性が低い、、、などの\witiko_parameter_1制御シーケンスをパラメータとして使用して、制御シーケンスを拡張します。\witiko_parameter_2\witiko_parameter_3
  2. #展開結果の s を2 倍にします。
  3. 展開結果の\witiko_parameter_1、、\witiko_parameter_2などを、、などに置き換えます。\witiko_parameter_3#1#2#3

具体的な実装は次のとおりです。

\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
  }

実装のデモは次のとおりです。

\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

これで、\greet { world } { favorite~species~(humans,~presumably) } { evolutionary~leap }次の展開を記述して受け取ることができるようになりました。

こんにちは、世界!あなたの好きな種族(おそらく人間)はどうですか?素晴らしい進化の飛躍を!

予想通りですね。

答え4

#これを、2 つの制限付きで、二重パラメータ文字 ( )、区切り引数、および任意のエンジンで動作させることができます。

  1. ターゲットマクロできない"FFマクロパラメータ文字として使用します。

  2. 任意の区切り文字しなければならない「通常の」catcodes を持っています。

マクロの 99% 以上がこれらの要件を満たすと予想されるため、実際にはそれほど制限的ではないはずです。

\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}

出力

ここでの秘訣は、ターゲット マクロを展開するときに異なるマクロ パラメータ文字を使用して、「内部」のネストされたパラメータと最も外側の「実際の」パラメータを区別できるようにすることです。

関連情報