4

I would like to save the content of an environment to use later with a macro at several places.

Here is the real use case.

  1. I have an environment to draw "probability" trees.
  2. I want to save the code used to draw the tree (it uses the syntax of forest). This needs to use an ID to identify the content.
  3. Then later I would like to use the raw content with a macro by giving the ID: this macro will analyze the code used to draw the tree such as to make several calculi automatically (this part will be done using Lua and I know how to do that).

What has been typed is what the Lua code analyzes.

The code used in the environment looks like this.

\begin{probatree}
    [
        [A, pweight = a
            [B, apweight* = b]
            [C, bpweight* = c]
        ]
        [D, pweight* = d]
    ]
\end{probatree}

Here is a starting MWE.

\documentclass{article}

\usepackage{luacode}
\usepackage{forest}   

\begin{luacode}
function calctree(a)
    tex.print("-+-+")
    tex.print("")
    tex.print("$" .. a .. "$")
    tex.print("")
    tex.print("-+-+")
end    
\end{luacode}

% This fully expands the argument
\newcommand\calctree[1]{\directlua{calctree("#1")}}%

\newenvironment{multilines}{}{}

\begin{document}    

\calctree{OOO}

\calctree{a (b + 3) - c^2}

% Save the content of the environment to use it later: this needs to give
% a single ID to the environment, this ID will be used to store internally
% the content.
%
% ????
\begin{multilines}
    A
    
    B
    
    C
\end{multilines}

Bla, bla, ...

% Use the content with \calctree.
%
% ????

Blo, blo, ...

% Use the content another time with \calctree.
%
% ????

\end{document}
7
  • 1
    an id is easy, the main question is how you want to save the content, the options are storing in a box, writing to a file, or using the b argument of xparse and storing in a macro. They have have slight differences, but your description doesn't allow to decide which you need. Apr 1, 2021 at 7:35
  • @UlrikeFischer I want to store the content as à raw string to send it to my Lua macro: what has been typed is what the Lua code analyzes.
    – projetmbc
    Apr 1, 2021 at 7:59
  • Is it the multilines environment content you wish to use again and again? If so, \def\z{% A B C } \begin{multilines} \z \end{multilines} \expandafter\calctree\expandafter{\z} \expandafter\calctree\expandafter{\z} Apr 1, 2021 at 9:32
  • Finally, I accept the answer of NBur. I will build a macro to do the saving and then call the environment to do the formatting. Thanks for all!
    – projetmbc
    Apr 1, 2021 at 10:11
  • 1
    It's hard to understand how you want to use the contents of multilines in context with \calctree. Please, explain.
    – egreg
    Apr 1, 2021 at 10:42

3 Answers 3

2

Due to the 30000-character-limit for answers I needed to split my answer in two parts.

This is part 2 of my answer.

Part 1 of my answer holds general explanations of the workings of TeX and of the code/working example provided in part 2.

Part 2 of my answer holds the working example.


\makeatletter
%%///////////// Code for interface \UD@GatherCodeSnippetLoop and ///////////////
%%///////////// environment DefineCodeSnippet and all suplementary things //////
%%===============================================================================
%% End \romannumeral-driven expansion safely:
%%===============================================================================
\@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\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\expandafter\UD@stopromannumeral\@secondoftwo}{%
  \expandafter\UD@stopromannumeral\@firstoftwo}%
}%
%%===============================================================================
%% Obtain control sequence token from name of control sequence token:
%%===============================================================================
%% \CsNameToCsToken<stuff not in braces>{NameOfCs}
%% ->  <stuff not in braces>\NameOfCs
%% (<stuff not in braces> may be empty.)
\@ifdefinable\CsNameToCsToken{%
  \long\def\CsNameToCsToken#1#{\romannumeral\InnerCsNameToCsToken{#1}}%
}%
\newcommand\InnerCsNameToCsToken[2]{%
  \expandafter\UD@exchange\expandafter{\csname#2\endcsname}{\UD@stopromannumeral#1}%
}%
\newcommand\UD@exchange[2]{#2#1}%
%%===============================================================================
%% Check if string of non-special character-tokens is substring
%% of another string of non-special character-tokens:
%%===============================================================================
%% \UD@checkstringsubsetof{<possible substring>}{<string>}%
%%   {<tokens if <possible substring> and <string> are equal>}%
%%   {<tokens if <possible substring> is strict subset of <string>>}%
%%   {<<tokens if <possible substring> is not subset of <string>>}%
%% The length of <possible substring> must not exceed the length of <string>!!!
\newcommand\UD@checkstringsubsetof[5]{%
  \UD@checkstringsubsetofloop#1\relax#2\relax{{#3}{#4}}{#5}%
}%
\@ifdefinable\UD@checkstringsubsetofloop{%
  \def\UD@checkstringsubsetofloop#1#2\relax#3#4\relax{%
    \if\string#1\string#3\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {%
      \ifx\relax#2\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
      {%
        \ifx\relax#4\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
        {\expandafter\@firstoftwo\@firstoftwo}{\expandafter\@secondoftwo\@firstoftwo}%
      }%
      {\UD@checkstringsubsetofloop#2\relax#4\relax}%
    }{\expandafter\@secondoftwo}%
  }%
}%
%%===============================================================================
%% Gather non-special character-tokens until encountering
%% \end{<name of environment>} :
%%===============================================================================
%% \UD@GatherCodeSnippetLoop{<non-special characters gathered so far>}%
%%                          {<subset of name of environment gathered so far>}%
%%                          {<tokens to apply to stuff gathered>}%
%%                          <non-special characters>\end{<name of environment>}
%%
%% Character-wise gathers <non-special characters>, replaces any non-space by its
%% \catcode-12-pendant. (Space is handled by giving it catcode 12 before applying 
%% the routine.) 
%% Then does 
%%  <tokens to apply to stuff gathered>{<non-special characters gathered so far>}
\newcommand\UD@GatherCodeSnippetLoop{%
  \expandafter\UD@@GatherCodeSnippetLoop\expandafter{\@currenvir}%
}%
\newcommand\UD@@GatherCodeSnippetLoop[1]{%
  \end{#1}%
  \begingroup
  \let\do\@makeother % <- this and the next line switch to
  \dospecials        %    verbatim-category-code-régime.
  \do\^^I%
  \do\^^M%
  \begingroup
  \escapechar=-1\relax
  \edef\@tempa{\string\\end\string{#1\string}}%
  \expandafter\endgroup
  \expandafter\UD@@@GatherCodeSnippetLoop
  \expandafter{\@tempa}%
}%
\newcommand\UD@@@GatherCodeSnippetLoop[5]{%
  \UD@checkstringsubsetof{#3#5}{#1}{%
    \expandafter\expandafter\expandafter\UD@exchange
    \expandafter\expandafter\expandafter{%
    \expandafter\expandafter\expandafter{%
      \UD@RemoveLeadingNTrailingCarriageReturn{#2}%
    }}{#4}%
  }{%
    \expandafter\UD@exchange\expandafter{\expandafter{%
      \romannumeral
      \expandafter\UD@exchange\expandafter{%
        \romannumeral\UD@StringifyIfNotSpace{#5}%
      }{\UD@stopromannumeral#3}%
    }}{\UD@@@GatherCodeSnippetLoop{#1}{#2}}{#4}%
  }{%
    \UD@checkstringsubsetof{#5}{#1}{%
      \expandafter\expandafter\expandafter\UD@exchange
      \expandafter\expandafter\expandafter{%
      \expandafter\expandafter\expandafter{%
        \UD@RemoveLeadingNTrailingCarriageReturn{#2#3}%
      }}{#4}%
    }{%
      \expandafter\UD@exchange\expandafter{\expandafter{%
         \romannumeral\UD@StringifyIfNotSpace{#5}%
      }}{\UD@@@GatherCodeSnippetLoop{#1}{#2#3}}{#4}%
    }{%
      \expandafter\UD@exchange\expandafter{\expandafter{%
        \romannumeral
        \expandafter\UD@exchange\expandafter{%
          \romannumeral\UD@StringifyIfNotSpace{#5}%
        }{\UD@stopromannumeral#2#3}%
      }}{\UD@@@GatherCodeSnippetLoop{#1}}{}{#4}%
    }%
  }%
}%
\newcommand\UD@StringifyIfNotSpace[1]{%
  \if\string#1 \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
  {\UD@stopromannumeral}{\expandafter\UD@stopromannumeral\string}#1%
}%
%%===============================================================================
%% Catcode-régime for defining components used by \UD@GatherCodeSnippetLoop
%% and for defining user-level-macros/environments using \UD@GatherCodeSnippetLoop
%% via \begingroup<catcode-settings>\@firstofone{\endgroup<Definitions>}-trick:
%%===============================================================================
\begingroup
\@makeother\^^M%
\catcode`\Z=14\relax%
\@makeother\%Z
\@firstofone{Z
  \endgroupZ
  ZZ=============================================================================
  ZZ Begin of code for removing one leading and one trailing explicit
  ZZ <carriage-return>-character-token of catcode 12(other) from _verbatimized_ 
  ZZ argument
  ZZ=============================================================================
  ZZ Check whether_verbatimized_ argument has a leading explicit 
  ZZ <carriage-return>-character-token of catcode 12(other):
  ZZ-----------------------------------------------------------------------------
  ZZ \UD@CheckWhetherLeadingCarriageReturn{<Argument which is to be checked>}%
  ZZ                                      {<Tokens to be delivered in case
  ZZ                                       <argument which is to be checked>'s
  ZZ                                       1st token is an explicit <carriage-
  ZZ                                       return>-character-token of 
  ZZ                                       catcode 12(other)>}%
  ZZ                                      {<Tokens to be delivered in case
  ZZ                                       <argument which is to be checked>'s
  ZZ                                       1st token is not an explicit
  ZZ                                       <carriage-return>-character-token of 
  ZZ                                       catcode 12(other)>}%
  \newcommand\UD@CheckWhetherLeadingCarriageReturn[1]{Z
    \UD@@CheckWhetherLeadingCarriageReturn\UD@SelDom#1\UD@SelDom^^M\UD@@SelDomZ
  }Z
  \@ifdefinable\UD@@CheckWhetherLeadingCarriageReturn{Z
    \long\def\UD@@CheckWhetherLeadingCarriageReturn#1\UD@SelDom^^M#2\UD@@SelDom{Z
      \UD@CheckWhetherNull{#2}{\@secondoftwo}{\@firstoftwo}Z
    }Z
  }Z
  ZZ-----------------------------------------------------------------------------
  ZZ Check whether_verbatimized_ argument having a leading explicit <carriage-
  ZZ return>-character-token of catcode 12(other) consists only of such tokens:
  ZZ-----------------------------------------------------------------------------
  ZZ \UD@CheckWhetherOnlyCarriageReturn{<Argument which is to be checked and 
  ZZ                                    which is known to have a leading explicit
  ZZ                                    <carriage-return>-character-token of
  ZZ                                    catcode 12(other)>}%
  ZZ                                   {<Tokens to be delivered in case <argument
  ZZ                                    which is to be checked> consists only
  ZZ                                    of explicit <carriage-return>-character-
  ZZ                                    tokens of catcode 12(other)>}%
  ZZ                                   {<Tokens to be delivered in case <argument
  ZZ                                    which is to be checked> does not consist
  ZZ                                    only of explicit <carriage-return>-
  ZZ                                    character-tokens of catcode 12(other)>}%
  \newcommand\UD@CheckWhetherOnlyCarriageReturn[1]{Z
    \UD@CheckWhetherLeadingCarriageReturn{#1}{Z
      \expandafter\UD@CheckWhetherOnlyCarriageReturnZ
      \expandafter{\UD@@TrimLeadingCarriageReturn#1}Z
    }{Z
      \UD@CheckWhetherNull{#1}{\@firstoftwo}{\@secondoftwo}Z
    }Z
  }Z
  ZZ-----------------------------------------------------------------------------
  ZZ Remove one leading explicit <carriage-return>-character-token of 
  ZZ catcode 12(other) from _verbatimized_ argument:
  ZZ-----------------------------------------------------------------------------
  \@ifdefinable\UD@@TrimLeadingCarriageReturn{Z
    \long\def\UD@@TrimLeadingCarriageReturn^^M{}Z
  }Z
  ZZ-----------------------------------------------------------------------------
  ZZ Check whether_verbatimized_ argument has a trailing explicit 
  ZZ <carriage-return>-character-token of catcode 12(other):
  ZZ-----------------------------------------------------------------------------
  ZZ \UD@CheckWhetherTrailingCarriageReturn{<Argument which is to be checked>}%
  ZZ                                      {<Tokens to be delivered in case
  ZZ                                       <argument which is to be checked>'s
  ZZ                                       last token is an explicit <carriage-
  ZZ                                       return>-character-token of 
  ZZ                                       catcode 12(other)>}%
  ZZ                                      {<Tokens to be delivered in case
  ZZ                                       <argument which is to be checked>'s
  ZZ                                       last token is not an explicit
  ZZ                                       <carriage-return>-character-token of 
  ZZ                                       catcode 12(other)>}%
  \newcommand\UD@CheckWhetherTrailingCarriageReturn[1]{Z
    \UD@@CheckWhetherTrailingCarriageReturn#1\UD@SelDom^^M\UD@SelDom\UD@@SelDomZ
  }Z
  \@ifdefinable\UD@@CheckWhetherTrailingCarriageReturn{Z
    \long\def\UD@@CheckWhetherTrailingCarriageReturn#1^^M\UD@SelDom#2\UD@@SelDom{Z
      \UD@CheckWhetherNull{#2}{\@secondoftwo}{\@firstoftwo}Z
    }Z
  }Z
  ZZ-----------------------------------------------------------------------------
  ZZ Remove one trailing explicit <carriage-return>-character-token of 
  ZZ catcode 12(other) from _verbatimized_ argument:
  ZZ-----------------------------------------------------------------------------
  \newcommand\UD@TrimTrailingCarriageReturn[1]{Z
     \UD@@TrimTrailingCarriageReturn#1\UD@SelDomZ
  }Z
  \@ifdefinable\UD@@TrimTrailingCarriageReturn{Z
    \long\def\UD@@TrimTrailingCarriageReturn#1^^M\UD@SelDom{#1}Z
  }Z
  ZZ-----------------------------------------------------------------------------
  ZZ Remove one leading and one trailing explicit <carriage-return>-character-
  ZZ token of catcode 12(other) from _verbatimized_ argument if present.
  ZZ In the edge case of the _verbatimized_ argument consisting only of explicit
  ZZ <carriage-return>-character-tokens of catcode 12(other) remove only one of 
  ZZ them.
  ZZ Due to \romannumeral-expansion the result is delivered in 2 expansion-steps:
  ZZ-----------------------------------------------------------------------------
  \newcommand\UD@RemoveLeadingNTrailingCarriageReturn[1]{Z
    \romannumeralZ
    \UD@CheckWhetherLeadingCarriageReturn{#1}{Z
      \UD@CheckWhetherTrailingCarriageReturn{#1}{Z
         \UD@CheckWhetherOnlyCarriageReturn{#1}{Z
           \expandafter\UD@stopromannumeral\UD@@TrimLeadingCarriageReturn#1Z
         }{Z
           \expandafter\expandafter\expandafter\expandafter\expandafterZ
           \expandafter\expandafter\UD@stopromannumeral\expandafterZ
           \UD@TrimTrailingCarriageReturn\expandafter{\UD@@TrimLeadingCarriageReturn#1}Z
         }Z
      }{Z
        \expandafter\UD@stopromannumeral\UD@@TrimLeadingCarriageReturn#1Z
      }Z
    }{Z
      \UD@CheckWhetherTrailingCarriageReturn{#1}{Z
        \expandafter\expandafter\expandafter\UD@stopromannumeralZ
        \UD@TrimTrailingCarriageReturn{#1}Z
      }{\UD@stopromannumeral#1}Z
    }Z
  }Z
  ZZ=============================================================================
  ZZ End of code for removing one leading and one trailing explicit
  ZZ <carriage-return>-character-token of catcode 12(other) from _verbatimized_ 
  ZZ argument
  ZZ=============================================================================
  ZZ Begin of code for replacing explicit <carriage-return>-character-token of 
  ZZ catcode 12(other)(^^M) by explicit <line-feed>-character-token of catcode
  ZZ 12(other) (^^J).
  ZZ In TeX ^^M usually is the endline-character appended at the ends of lines
  ZZ of .tex-input when pre-processing them (by TeX's eyes) right before
  ZZ tokenization.
  ZZ In TeX ^^J usually is the newline-character which is not written but yields
  ZZ a transition from one line to the next line when writing to file/screen, 
  ZZ e.g., while \scantokens does its fake-writing.
  ZZ=============================================================================
  \newcommand\UD@ReplaceCarriageReturnByLineFeed[1]{Z
    \romannumeral\@UDReplaceCarriageReturnByLineFeed{^^J}#1^^M\relax{}Z
  }Z
  \@ifdefinable\@UDReplaceCarriageReturnByLineFeed{Z
    \long\def\@UDReplaceCarriageReturnByLineFeed#1#2^^M#3\relax#4#5{Z
      \UD@CheckWhetherNull{#3}Z
        {\UD@stopromannumeral#5{#4#2}}Z
        {\@UDReplaceCarriageReturnByLineFeed{#1}#3\relax{#4#2#1}{#5}}Z
    }Z
  }Z
  ZZ=============================================================================
  ZZ End of code for replacing explicit <carriage-return>-character-token of 
  ZZ catcode 12(other)(^^M) by explicit <line-feed>-character-token of catcode
  ZZ 12(other) (^^J).
  ZZ=============================================================================
  ZZ Begin of code for environment DefineCodeSnippet
  ZZ=============================================================================
  \newenvironment{DefineCodeSnippet}[1]{Z
    \UD@GatherCodeSnippetLoop{}{}Z
                             {\CsNameToCsToken\endgroup\newcommand*{CodeSnippet#1}}Z
  }{}Z
  ZZ=============================================================================
  ZZ End of code for environment DefineCodeSnippet
  ZZ=============================================================================
  ZZ=============================================================================
  ZZ Begin of code for environment RedefineCodeSnippet
  ZZ=============================================================================
  \newenvironment{RedefineCodeSnippet}[1]{Z
    \UD@GatherCodeSnippetLoop{}{}Z
                             {\CsNameToCsToken\endgroup\renewcommand*{CodeSnippet#1}}Z
  }{}Z
  ZZ=============================================================================
  ZZ End of code for environment RedefineCodeSnippet
  ZZ=============================================================================
  ZZ=============================================================================
  ZZ Begin of code for environment AppendToCodeSnippet
  ZZ=============================================================================
  \newenvironment{AppendToCodeSnippet}[1]{Z
    \UD@GatherCodeSnippetLoop{}{}{\endgroup\UD@AppendToCodeSnippet{#1}}Z
  }{}Z
  \newcommand\UD@AppendToCodeSnippet[2]{Z
    \@ifundefined{CodeSnippet#1}{Z
      \@latex@error{CodeSnippet \string#1 undefined}\@ehcZ
    }{Z
      \CsNameToCsToken\expandafter\renewcommand\expandafter*\expandafter{CodeSnippet#1}Z
      \expandafter{\romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}^^M#2}Z
    }Z
  }Z
  ZZ=============================================================================
  ZZ End of code for environment AppendToCodeSnippet
  ZZ=============================================================================
  ZZ=============================================================================
  ZZ Begin of code for environment PrependToCodeSnippet
  ZZ=============================================================================
  \newenvironment{PrependToCodeSnippet}[1]{Z
    \UD@GatherCodeSnippetLoop{}{}Z
                             {\endgroup\UD@PrependToCodeSnippet{#1}}Z
  }{}Z
  \newcommand\UD@PrependToCodeSnippet[2]{Z
    \@ifundefined{CodeSnippet#1}{Z
      \@latex@error{CodeSnippet \string#1 undefined}\@ehcZ
    }{Z
      \CsNameToCsTokenZ
      \expandafter\renewcommand\expandafter*\expandafter{CodeSnippet#1}Z
      \expandafter{Z
        \romannumeral\expandafter\UD@exchange\expandafter{Z
          \romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}Z
        }{\UD@stopromannumeral#2^^M}Z
      }Z
    }Z
  }Z
  ZZ=============================================================================
  ZZ End of code for environment PrependToCodeSnippet
  ZZ=============================================================================
  \newcommand\ExecuteCodeSnippet[1]{Z
    \expandafter\UD@ReplaceCarriageReturnByLineFeed\expandafter{Z
       \romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}%Z
     }{\scantokens}Z
  }Z
  \newcommand\DeliverCodeSnippetToMacro[2]{Z
    \expandafter\UD@exchange\expandafter{\expandafter{\romannumeralZ
       \CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}Z
    }}{#2}Z
  }Z
  \newcommand\DeliverCodeSnippetToTokens[2]{Z
    \DeliverCodeSnippetBetweenTokens{#1}{#2}{}Z
  }Z
  \newcommand\DeliverCodeSnippetBetweenTokens[3]{Z
    \expandafter\UD@exchange\expandafter{\romannumeralZ
       \CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}Z
    }{#2}#3Z
  }Z
  \newcommand\ShowCodeSnippet[1]{Z
     \CsNameToCsToken\show{CodeSnippet#1}Z
  }Z
  \newcommand\MeaningCodeSnippet[1]{Z
     \CsNameToCsToken\meaning{CodeSnippet#1}Z
  }Z
  \newcommand\ConcatCodeSnippets{Z
    \@ifstar{\ConcatCodeSnippetsAtStarNoStar{\def}}Z
            {\ConcatCodeSnippetsAtStarNoStar{\newcommand*}}Z
  }Z
  \newcommand\ConcatCodeSnippetsAtStarNoStar[4]{Z
    \@ifundefined{CodeSnippet#2}{Z
      \@latex@error{CodeSnippet \string#2 undefined}\@ehcZ
      \@ifundefined{CodeSnippet#3}{Z
        \@latex@error{CodeSnippet \string#3 undefined}\@ehcZ
      }{Z
         \expandafter\UD@exchange\expandafter{\expandafter{Z
           \romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#3}Z
         }}{\CsNameToCsToken#1{CodeSnippet#4}}Z
      }Z
    }{Z
      \@ifundefined{CodeSnippet#3}{Z
        \@latex@error{CodeSnippet \string#3 undefined}\@ehcZ
        \expandafter\UD@exchange\expandafter{\expandafter{Z
          \romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#2}Z
        }}{\CsNameToCsToken#1{CodeSnippet#4}}Z
      }{Z
        \expandafter\UD@exchange\expandafter{\expandafter{Z
          \romannumeralZ
          \expandafter\UD@exchange\expandafter{Z
            \romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#3}Z
          }{Z
            \CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#2}^^MZ
          }Z
        }}Z
        {\CsNameToCsToken#1{CodeSnippet#4}}Z
      }Z
    }Z
  }Z
}%
%%///////////// End of code for interface \UD@GatherCodeSnippetLoop and ////////
%%///////////// environment DefineCodeSnippet and all suplementary things //////
\makeatother

\documentclass[a4paper]{article}
\pagestyle{empty}
\addtolength\textwidth{7cm}
\addtolength\evensidemargin{-3.5cm}
\addtolength\oddsidemargin{-3.5cm}
\enlargethispage{4cm}
\partopsep=0ex
\topsep=0ex

\begin{document}
\vspace*{-4.75cm}%
\begingroup
\catcode`\^^M=12\relax%
\catcode`\^^J=12\relax%
\leftmargini=\labelsep%
\settowidth\labelwidth{\texttt{\string^^M}}%
\advance\leftmargini\labelwidth%
\begin{itemize}%
\item[\texttt{\string^^M}]in cmtt-font denotes the \textit{\textlangle carriage-return\textrangle}-character %
(code-point-number 13 both in ASCII and in Unicode, \verb|^^M| in TeX's \verb|^^|\hbox{-}notation, ``M'' being %
the \(13^{\hbox{\tiny th}}\) letter of the uppercase-alphabet).\par%
\item[\texttt{\string^^J}] in cmtt-font denotes the \textit{\textlangle line-feed\textrangle}-character %
(code-point-number 10 both in ASCII and in Unicode, \verb|^^J| in TeX's \verb|^^|\hbox{-}notation, ``J'' being %
the \(10^{\hbox{\tiny th}}\) letter of the uppercase-alphabet).%
\end{itemize}%
\endgroup 

\hrule\vfill

\noindent Define a code-snippet \verb|foobar1|:\vfill

\begin{verbatim}
\begin{DefineCodeSnippet}{foobar1}
Text Text    Text
Text
\end{verbatim*}
\end{DefineCodeSnippet}
\end{verbatim}

\begin{DefineCodeSnippet}{foobar1}
Text Text    Text
Text
\end{verbatim*}
\end{DefineCodeSnippet}

\vfill\hrule\vfill

\noindent\verb|\texttt{\MeaningCodeSnippet{foobar1}| yields:\vfill

\noindent\texttt{\MeaningCodeSnippet{foobar1}}%

\vfill\hrule\vfill

\noindent Define a code-snippet \verb|foobar2| with two empty lines:\vfill

\begin{verbatim}
\begin{DefineCodeSnippet}{foobar2}


\end{DefineCodeSnippet}
\end{verbatim}

\begin{DefineCodeSnippet}{foobar2}


\end{DefineCodeSnippet}

\vfill\hrule\vfill

\noindent\verb|\texttt{\MeaningCodeSnippet{foobar2}| yields:\vfill

\noindent\texttt{\MeaningCodeSnippet{foobar2}}%

\vfill\hrule\vfill

\noindent Prepend to code-snippet \verb|foobar1|:\vfill

\begin{verbatim}
\begin{PrependToCodeSnippet}{foobar1}
\begin{verbatim*}
\end{PrependToCodeSnippet}
\end{verbatim}

\begin{PrependToCodeSnippet}{foobar1}
\begin{verbatim*}
\end{PrependToCodeSnippet}

\vfill\hrule\vfill

\noindent Append to code-snippet \verb|foobar1|:\vfill

\begin{verbatim}
\begin{AppendToCodeSnippet}{foobar1}

\noindent\TeX\space is {\huge funny}%
\end{AppendToCodeSnippet}
\end{verbatim}

\begin{AppendToCodeSnippet}{foobar1}

\noindent\TeX\space is {\huge funny}%
\end{AppendToCodeSnippet}

\vfill\hrule\vfill

\noindent\verb|\texttt{\MeaningCodeSnippet{foobar1}| yields:\vfill

\noindent\texttt{\MeaningCodeSnippet{foobar1}}%

\vfill\hrule\vfill

\noindent This yields console-output:\vfill

\noindent\verb|\ShowCodeSnippet{foobar1}|%
\ShowCodeSnippet{foobar1}%

\vfill\hrule\vfill

\noindent\verb|\ExecuteCodeSnippet{foobar1}| yields:\vfill

\ExecuteCodeSnippet{foobar1}%

\vfill\hrule\vfill

\noindent\verb|\DeliverCodeSnippetToTokens{foobar1}{\begin{verbatim}}\end{verbatim}| yields:\vfill

\noindent\DeliverCodeSnippetToTokens{foobar1}{\begin{verbatim}}\end{verbatim}%

\vfill\hrule\vfill

\noindent\verb|\DeliverCodeSnippetToMacro{foobar1}{\texttt}| yields:\vfill

\noindent\DeliverCodeSnippetToMacro{foobar1}{\texttt}%

\vfill\hrule\vfill

\noindent Concatenate code-snippets \verb|foobar1| and \verb|foobar1| as code-snippet \verb|foobarDouble|:\vfill

\noindent\verb|\ConcatCodeSnippets{foobar1}{foobar1}{foobarDouble}|%
\ConcatCodeSnippets{foobar1}{foobar1}{foobarDouble}%

\vfill\hrule\vfill

\noindent This yields console-output:\vfill

\noindent \verb|\ShowCodeSnippet{foobarDouble}|%
\ShowCodeSnippet{foobarDouble}%

\vfill\hrule\vfill

\noindent\verb|\ExecuteCodeSnippet{foobarDouble}| yields:\vfill

\ExecuteCodeSnippet{foobarDouble}%

\vfill

\end{document}

pdf-output:

enter image description here

console-output:

This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2020-10-01> patch level 4
L3 programming layer <2021-02-18>
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/article.cls
Document Class: article 2020/04/10 v1.4m Standard LaTeX document class
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/size10.clo))
(/usr/local/texlive/2020/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def)
(./test.aux)
Overfull \hbox (3.08784pt too wide) in paragraph at lines 437--440
[]\OT1/cmr/m/n/10 in cmtt-font de-notes the \TS1/cmr/m/it/10 <\OT1/cmr/m/it/10 
line-feed\TS1/cmr/m/it/10 >\OT1/cmr/m/n/10 -character (code-point-number 10 bot
h in ASCII and in Uni-code, [][]\OT1/cmtt/m/n/10 ^^J []\OT1/cmr/m/n/10 in TeX's
 [][]\OT1/cmtt/m/n/10 ^^[][]\OT1/cmr/m/n/10 notation,
> \CodeSnippetfoobar1=macro:
->\begin{verbatim*}^^MText Text    Text^^MText^^M\end{verbatim*}^^M^^M\noindent
\TeX\space is {\huge funny}%.
<argument> \CodeSnippetfoobar1 
                               
l.530 \ShowCodeSnippet{foobar1}
                               %
? 
> \CodeSnippetfoobarDouble=macro:
->\begin{verbatim*}^^MText Text    Text^^MText^^M\end{verbatim*}^^M^^M\noindent
\TeX\space is {\huge funny}%^^M\begin{verbatim*}^^MText Text    Text^^MText^^M\
end{verbatim*}^^M^^M\noindent\TeX\space is {\huge funny}%.
<argument> \CodeSnippetfoobarDouble 
                                    
l.562 \ShowCodeSnippet{foobarDouble}
                                    %
? 
[1{/usr/local/texlive/2020/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
(./test.aux) ){/usr/local/texlive/2020/texmf-dist/fonts/enc/dvips/cm-super/cm-s
uper-ts1.enc}</usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm
/cmr10.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/c
mr17.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/cmr
5.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10
.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.
pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/cm-super/sfti1000.pf
b>
Output written on test.pdf (1 page, 71882 bytes).
Transcript written on test.log.
1
  • Thanks, for this code!
    – projetmbc
    Apr 15, 2021 at 22:41
3

Due to the 30000-character-limit for answers I needed to split my answer in two parts.

This is part 1 of my answer.

Part 1 of my answer holds general explanations of the workings of TeX and of the code/working example provided in part 2.

Part 2 of my answer holds the working example.


You use the phrase "raw content" without clarifying what it shall mean/denote.

I can offer a generic interface \UD@GatherCodeSnippetLoop which switches to verbatim-catcode-régime (category codes of special characters ⟨space-character⟩, \, {, }, $, &, #, ^, _, %, ~ switched to 12(other)) and character by character reads from the .tex-input-file and tokenizes and accumulates things until encountering a character-sequence \end{⟨expansion of \@currenvir⟩}, applying \string to every accumulated non-space-character for transforming everything, e.g., characters usually active due to the package inputenc, to catcode 12(other). (Applying \string to non-space-character-tokens only is for the following reason: Space-characters already have category-code 12 due to the switching to verbatim-catcode-régime. Applying \string would transform them into explicit space-tokens of category code 10(space) as spaces/space-tokens are the only exception with \string: \string transforms everything but spaces to category code 12(other) and transforms every space to category code 10(space).  )

\UD@GatherCodeSnippetLoop can be used for defining different environments with differing behavior, having in common that the body of the environment is gathered from the .tex-input-file as a sequence of explicit character tokens of category code 12(other).

Such environments can be used for storing these sequences of explicit character tokens of category code 12(other) as macros.

After expanding these macros, processing the sequences of explicit character tokens of category code 12(other) by \scantokens yields the same as processing the characters forming bodies of the environments via TeX's eyes/mouth/gullet/stomach etc directly without the actions of switching catcode-régime and saving things away—unless the catcode-régime in effect at the time of carrying out such an environment differs from the catcode-régime in effect at the time of carrying out the macro/at the time of \scantokens' retokenization of the single characters that formed the body of the environment.

In the example in part 2 the generic interface \UD@GatherCodeSnippetLoop is defined and environments DefineCodeSnippet, RedefineCodeSnippet, PrependToCodeSnippet, AppendToCodeSnippet based on \UD@GatherCodeSnippetLoop are defined. Additionally user-level-macros \ConcatCodeSnippets, \ExecuteCodeSnippet, \DeliverCodeSnippetToMacro, \DeliverCodeSnippetToTokens are defined. All the environments and user-level-macros also process some argument/arguments denoting IDs of code-snippets.


\begin{DefineCodeSnippet}{⟨ID⟩}
Text Text    Text
Text
\end{DefineCodeSnippet}

reads the body of the environment from the .tex-input-file as a sequence of explicit catcode-12(other)-character-tokens and defines the macro \CodeSnippet⟨ID⟩ to yield this sequence of catcode-12(other)-character-tokens by toplevel-expansion.


\begin{RedefineCodeSnippet}{⟨ID⟩}
Text Text    Text
Text
\end{RedefineCodeSnippet}

redefines \CodeSnippet⟨ID⟩ accordingly.


\begin{PrependToCodeSnippet}{⟨ID⟩}
Text Text    Text
Text
\end{PrependToCodeSnippet}

prepends lines to \CodeSnippet⟨ID⟩.


\begin{AppendToCodeSnippet}{⟨ID⟩}
Text Text    Text
Text
\end{AppendToCodeSnippet}

appends lines to \CodeSnippet⟨ID⟩.


The macro

\ConcatCodeSnippets{⟨ID of source snippet 1⟩}{⟨ID of source snippet 2⟩}{⟨target ID⟩}

defines \CodeSnippet⟨target ID⟩ to yield:

⟨sequence of explicit catcode-12(other)-character-tokens denoted by ID of source snippet 1⟩
⟨explicit ⟨carriage-return⟩-character-token of category code 12 (other)⟩
⟨sequence of explicit catcode-12(other)-character-tokens denoted by ID of source snippet 2⟩

The starred variant \ConcatCodeSnippets* allows to override/redefine \CodeSnippet⟨target ID⟩.


The macro

\ExecuteCodeSnippet{⟨ID⟩}

does

\scantokens\expandafter{\CodeSnippet⟨ID⟩%}

, the trailing % being of catcode 12(other) as well so that it will be written by \scantokens' fake-writing without problems and be recognized by \scantokens' reading-back under its usual catcode 14(comment) whereby it then prevents the coming into being of a spurious token due to TeX's insertion of the endline-character.

Before passing things to \scantokens ⟨carriage-return⟩-characters are replaced by ⟨line-feed⟩-characters.

In order to understand the reason for the latter you need to know about some details of how TeX works:

At the time when TeX reads a line of a .tex-input-file and pre-processes it, right before tokenization, a character is appended whose code-point-number in TeX's internal character-encoding-scheme (which with traditional TeX is ASCII and with LuaTeX/XeTeX is unicode) equals the value of the integer-parameter \endlinechar.
Usually the value of \endlinechar is 13 while code-point number 13 denotes the ⟨carriage-return⟩-character both in ASCII and in Unicode.
So usually a ⟨carriage-return⟩-character is appended while reading/preprocessing a line of .tex-input.
In TeX's ^^-notation the ⟨carriage-return⟩-character is also denotable as ^^M as M is the 13th letter in the uppercase-alphabet.

At the time when TeX writes explicit character-tokens unexpanded to external text file or screen, TeX with each explicit character-token checks whether its character-code (=the number of the code-point of the character in question in TeX's internal character-encoding-scheme) equals the value of the integer-parameter \newlinechar.
If so, then the corresponding character is not written but the character-token in question is taken for a signal to create in the external text file or on the screen a transition from the current line to a next line.
The result of this is platform-dependent. In text-files, depending on the platform, the ⟨carriage-return⟩-character or the ⟨line-feed⟩-character or a combination of both is used for denoting the transition from one line of a text to the next line of text.
Usually the value of \newlinechar is 10 while code-point number 10 denotes the ⟨line-feed⟩-character both in ASCII and in Unicode.
So at the time of writing things usually ⟨line-feed⟩-character-tokens are signals for creating a transition from one line of a text-file to the next line of that text-file.
In TeX's ^^-notation the ⟨line-feed⟩-character is also denotable as ^^J as J is the 10th letter in the uppercase-alphabet.

\scantokens processes a set of already tokenized tokens as follows:
First TeX fakes unexpanded-writing these tokens to external text file.
Then TeX uses that faked external file as souce of .tex-input (as if you had applied the \input-primitive for switching to another source of .tex-input) and reads/preprocesses/tokenizes/digests it outgoing from the catcode-régime which is current at the time of carrying out \scantokens.

In our situation the set of already tokenized tokens was tokenized under verbatim-catcode-régime, so that only (non-special) explicit character-tokens came into being, and each character-token was transformed to have category-code 12(other). This implies that the ⟨carriage-return⟩-characters, which—due to the above-mentioned \endlinechar-mechanism—were inserted at the ends of lines during the stage of pre-processing, got tokenized as (non-special) explicit character-tokens of category code 12(other) and character code 13.
Usually TeX writes such tokens to file in ^^-notation, i.e., instead of writing some combination of ⟨carriage-return⟩-characters and/or ⟨line-feed⟩-characters or whatever, the character-sequence ^, ^, M gets written, and characters written subsequently also end up in the same line.
If you want \scantokens' fake-writing to instead create a transition from one line to the next, you can either assign \newlinechar the value 13 right before applying \scantokens, or, remembering that the value of the \newlinechar-parameter usually denotes the ⟨line-feed⟩-character (10/^^J), you can have TeX replace each explicit catcode-12(other)-⟨carriage-return⟩-character-token by an explicit catcode-12(other)-⟨line-feed⟩-character-token before applying \scantokens.
I prefer the latter because it does not require changes of whatsoever parameters of TeX being in effect while \scantokens is carried out, in edge cases probably leading to behaviour deviating from what is considered the "usual thing".
( If you would let the fake-writing of \scantokens just write the sequence ^, ^, M instead of having the fake-writing-routine create a transition from one line to the next, inputting/reading/tokenizing/digesting/processing the fake-file under normal catcode-régime would be a problem: ^, ^, M would be taken for ^^M, i.e., for the ^^-notation of the ⟨carriage-return⟩-character. Under normal catcode-régime the ⟨carriage-return⟩-character has category code 5(end of line) which means that it causes TeX

  1. (!!!) to cease processing the line, which imples that every character written in the same line behind the character-sequence ^, ^, M by \santokens' fake-writing would be lost(!!!), and

  2. depending on the current state of the reading apparatus, to append to the token-stream that goes down TeX's gullet, either no token at all (state S), or an explicit space-token (state M), or the control-word-token \par (state N).

)


The macro

\DeliverCodeSnippetToMacro{⟨ID⟩}{⟨tokens⟩}

delivers

⟨tokens⟩{⟨expansion of \CodeSnippet⟨ID⟩⟩}

The macro

\DeliverCodeSnippetToTokens{⟨ID⟩}{⟨tokens⟩}

delivers

⟨tokens⟩⟨expansion of \CodeSnippet⟨ID⟩⟩

( The difference between \DeliverCodeSnippetToMacro and \DeliverCodeSnippetToTokens is that the further delivers ⟨expansion of \CodeSnippet⟨ID⟩⟩ nested between a pair of curly braces of catcode 1(begin group)/catcode 2(end group) while the latter delivers things without such a brace-pair. )


With the enviroments one leading ⟨carriage-return⟩-character (if present) and one trailing ⟨carriage-return⟩-character (if present) will be removed from the ⟨sequence of catcode-12-characters formed by the body of the environment⟩ before (re)defining \CodeSnippet⟨ID⟩.

This way

\begin{DefineCodeSnippet}{foobar}
Text Text    Text
Text
\end{DefineCodeSnippet}

yields the same as

\begin{DefineCodeSnippet}{foobar}Text Text    Text
Text\end{DefineCodeSnippet}

, namely:

\newcommand*\CodeSnippetfoobar{Text Text    Text⟨explicit ⟨carriage-return⟩-character-token of catcode 12(other)⟩Text}

; every token of \CodeSnippetfoobar's ⟨definition-text⟩ being an explicit character-token of category-code 12(other).

In the edge case of the body of the environment consisting of ⟨carriage-return⟩-characters only, only one of them is removed as in this case it is assumed that with n ⟨carriage-return⟩-characters one wishes to denote (n-1) empty lines. (The first ⟨carriage-return⟩-character denotes the transition from the line holding \begin{⟨environment⟩} to the first line holding "emptiness".)

Be aware that TeX's pre-processing of lines of .tex-input in any case removes sequences of ⟨space⟩-characters that occur right at the ends of lines of .tex-input.

Therefore you cannot preserve sequences of ⟨space⟩-characters that occur right at the ends of lines of .tex-input for re-processing by TeX (e.g. via \scantokens).

The environments DefineCodeSnippet etc rely on temporarily changing the category-code-régime and obtaining the tokens forming their body by reading/tokenizing content from .tex-input files while the changed category-code-régime is in effect.

Therefore the environments DefineCodeSnippet etc cannot be used inside macro-definitions/macro-arguments and the like where stuff gets tokenized already at the time of gathering the tokens that form the macro's definition/at the time of gathering the tokens that form the macro's argument(s).

2

You can store your content in a savebox. Thus I added another parameter to your \calctree: the ID (note it requires a backslash). Then you invoke \usebox\ID to use the box.

\documentclass{article}
\usepackage{luacode}

\begin{luacode}
    function calctree(a)
    tex.print("-+-+")
    tex.print("")
    tex.print("$" .. a .. "$")
    tex.print("")
    tex.print("-+-+")
    end    
\end{luacode}

% This fully expands the argument
\newcommand\calctree[2]{% second arg is a macroname to store the content
    \newsavebox{#2}
    \savebox{#2}{\directlua{calctree("#1")}}}%

\newenvironment{multilines}{}{}


\begin{document}    
    \calctree{OOO}{\boxOOO}
    
    \calctree{a (b + 3) - c^2}{\boxabc}
    
    \begin{multilines}
        A
        
        B
        
        C
    \end{multilines}
    
    Bla, bla, ...
    
    % Use the content with \calctree.
    \usebox\boxOOO
    
    Blo, blo, ...
    
    % Use the content another time with \calctree.
    \usebox\boxabc
    
    \usebox\boxOOO
    
\end{document}
1
  • 1
    Great but it misses a solution for the main problem: saving the raw content of the environment.
    – projetmbc
    Apr 1, 2021 at 7:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.