가변 개수의 무제한 매개변수가 포함된 제어 시퀀스를 토큰 목록으로 변환

가변 개수의 무제한 매개변수가 포함된 제어 시퀀스를 토큰 목록으로 변환

expl3에서는 토큰 목록을 다음과 같이 두 개의 매개변수를 사용하는 \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다시 토큰 목록으로 변환하여 제어 시퀀스로 다시 변환하기 전에 추가할 수 있도록 하고 싶습니다. 다음과 같이 두 매개변수에 대해 이 작업을 수동으로 수행할 수 있습니다.

\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 }다음 확장을 작성하고 받을 수 있습니다.

안녕, 세상! 당신이 가장 좋아하는 종(아마 인간)은 어떻게 지내나요? 위대한 진화적 도약을 이루세요!

그러나 나는 다음과 같이 단지 두 개가 아닌 여러 매개변수에 대해 작동하는 방식으로 이 작업을 수행할 수 있기를 원합니다.

\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의 답변과 달리 #대체 텍스트의 doubled 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를 사용할 수 있다면 구분된 인수가 있는 매크로에 대해서도 이 작업을 수행할 수 있습니다. 매크로 정의의 캣코드를 얻는 것은 예상보다 훨씬 어렵습니다. 따라서 이 작업을 수행하려면 많은 더러운 트릭을 사용해야 합니다.

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

산출

이를 작동시키는 데에는 두 가지 주요 "비법"이 있습니다.

  1. 이것이 일반적으로 작동하려면 Lua 문자열뿐만 아니라 토큰 목록(적절한 catcode 포함)으로 매크로를 정의해야 합니다. 따라서 매크로 정의를 얻으려면 먼저 빈 \mark노드를 만듭니다. 다음으로, 매크로 정의를 나타내는 정수로 내용을 설정하기 위해 몇 가지 트릭을 사용합니다. 그런 다음 노드 값을 요청하면 \mark실제로 Lua 테이블로 표시되는 매크로 내용을 얻습니다.

  2. 그런 다음 Lua 테이블의 토큰으로 레지스터를 설정하기 위해 \toks동일한 \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 }다음 확장을 다시 작성하고 받을 수 있습니다.

안녕, 세상! 당신이 가장 좋아하는 종(아마 인간)은 어떻게 지내나요? 위대한 진화적 도약을 이루세요!

expl3의 l3tl 모듈에 함수 \tl_set_from_cs:NN를 추가하려면 \tl_gset_from_cs:NN.

또한 무제한 매개변수가 있는 제어 시퀀스만 이 방식으로 토큰 목록으로 변환될 수 있다는 점을 명확하게 문서화해야 합니다.

편집: 추가 설명(2024-04-27)

#다음과 같이 대체 텍스트에서 이중 s를 유지할 수 있습니다 .

  1. 대체 텍스트에 나타날 가능성이 없는 \witiko_parameter_1, \witiko_parameter_2, 등과 같은 제어 시퀀스를 매개변수로 사용하여 제어 시퀀스를 확장합니다 .\witiko_parameter_3
  2. #확장 결과에 s를 두 배로 늘립니다 .
  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

#이중 매개변수 문자( ), 구분된 인수 및 두 가지 제한 사항이 있는 모든 엔진을 사용하여 이 작업을 수행할 수 있습니다 .

  1. 대상 매크로할 수 없다"FF매크로 매개변수 문자로 사용합니다 .

  2. 모든 구분 기호~ 해야 하다"정상적인" catcode를 가지고 있습니다.

매크로의 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}

산출

여기서의 비결은 "내부" 중첩 매개변수와 가장 외부의 "실제" 매개변수를 구별할 수 있도록 대상 매크로를 확장할 때 다른 매크로 매개변수 문자를 사용하는 것입니다.

관련 정보