% File: saveenv.sty
% Copyright 2022 user202729
%
% This work  may be  distributed and/or  modified under  the conditions  of the
% LaTeX Project Public License (LPPL),  either version 1.3c  of this license or
% (at your option) any later version.  The latest version of this license is in
% the file:
%
%   http://www.latex-project.org/lppl.txt
%
% This work has the LPPL maintenance status `maintained'.
% 
% The Current Maintainer of this work is user202729.

\ProvidesExplPackage{saveenv}{2022/12/23}{0.0.1}{Save environment content verbatim}
\RequirePackage{precattl}
\msg_new:nnn {saveenv} {trailing-content-or-pretokenized}
	{Trailing~content~found~on~line,~or~content~pretokenized!}

\msg_new:nnn {saveenv} {trailing-content-end-line}
	{Trailing~content~found~on~environment~last~line}

\msg_new:nnn {saveenv} {currfile-package-not-loaded}
	{currfile~package~is~needed~for~this~functionality}

\precattl_exec:n{
% args: char code, content, environment name
\msg_new:nnn {saveenv} {leading-content-last-line}
	{Leading~content~found~on~\cO\\end{#3}~line,~first~char~code~=~#1,~content~=~'#2'}
}

\makeatletter

\seq_new:N \_senv_lines
\ior_new:N \_senv_file

\precattl_exec:n {

\NewDocumentEnvironment {saveenvghost} {m} {
	\edef \_senv_firstline {\the\inputlineno}
} {

	\cs_if_free:NT \currfilename {
		\msg_error:nn {saveenv} {currfile-package-not-loaded}
	}
	\ior_open:Nn \_senv_file {\currfilename}

	\prg_replicate:nn {\_senv_firstline} {
		\ior_str_get:NN \_senv_file \_senv_line
	}

	\tl_build_gbegin:N #1

	\prg_replicate:nn {\the\inputlineno-\_senv_firstline-1} {
		\ior_str_get:NN \_senv_file \_senv_line
		\exp_args:NNo \tl_build_gput_right:Nn #1 {\_senv_line ^^J}
	}

	\ior_close:N \_senv_file

	\tl_build_gend:N #1

}


% #1: target macro
% #2: content to be inserted after the \end
\NewDocumentEnvironment {saveenvkeeplastreinsert} {mm} {
	\begingroup
	\edef \_senv_old_endlinechar {\the\endlinechar}
	%\bench before cctab.
	\cctab_select:N \c_other_cctab  % note that this changes the value of \endlinechar as well
	%\bench after cctab.
	\int_compare:nNnTF {\_senv_old_endlinechar} < {0} {
		\_senv_start_get_body:Nn #1 {#2}
	} {
		\exp_last_unbraced:Nf \peek_meaning_remove:NTF { \char_generate:nn{\_senv_old_endlinechar}{12} }    %12 is other
		{
			\_senv_start_get_body:Nn #1 {#2}
		}
		{
			\msg_error:nn {saveenv} {trailing-content-or-pretokenized}
		}
	}
} {
}


% #1: target macro
% #2: content to be inserted after the \end
\cs_new_protected:Npn \_senv_start_get_body:Nn #1 #2 {
	\endlinechar=10~
	\str_set:NV \_senv_env \@currenvir
	\tl_replace_all:Nnn \_senv_env {~} {\cO\  }
	\_senv_helper:NVVn #1 \_senv_env \@currenvir {#2}
}

% #1: target macro
% #2: value of \@currenvir but with all tokens catcode 12 (other)
% #3: value of \@currenvir
% #4 content to be inserted after the \end
\cs_new_protected:Npn \_senv_helper:Nnnn #1 #2 #3 #4 {
	\cs_set_protected:cpn {[saveenv]~verbatim~body~scanner~for~#2} ##1 \cO{ \\end\{ } #2 \cO\} {
		%\bench X3.
		% ##1: the body
		\peek_meaning_remove:NTF ^^J {
			%\bench inside peek.
			\endgroup
			%\bench after endgroup.
			\str_gset:Nn #1 {##1}
			%\bench X5.
			\end{#3}
			%\bench X6.
			#4
		} {
			\msg_error:nn {saveenv} {trailing-content-end-line}
		}
	}

	\use:c {[saveenv]~verbatim~body~scanner~for~#2}
}
\cs_generate_variant:Nn \_senv_helper:Nnnn {NVVn}

\NewDocumentEnvironment {saveenvkeeplast} {m} {
	\saveenvkeeplastreinsert #1 {}
} {
	\endsaveenvkeeplastreinsert
}

% set variable #1 to have content of #2, but with last line dropped.
% lines are separated by \^^J.

\cs_new:Npn \_senv_append_newline:n #1 { #1 ^^J }

\cs_new_protected:Npn \saveenv_set_drop_last:Nn #1 #2 {
	\tl_set:Nn \_senv_body {#2}
	\tl_replace_all:Nnn \_senv_body {~} {\cO\  }  % keep spaces in seq_set_split (support older expl3 versions)
	\seq_set_split:NnV \_senv_lines {^^J} \_senv_body
	\seq_pop_right:NN \_senv_lines \_senv_lastline
	\tl_map_inline:Nn \_senv_lastline {  % debug check, ensure last line is empty
		\int_case:nnF {`##1}  {
			{32} {}  %space
			{9} {}   %tab
		}
		{
			\msg_error:nnoVV {saveenv} {leading-content-last-line} {\number`##1} \_senv_lastline \@currenvir
		}
	}
	%\str_gset:Nx #1 {\seq_use:Nn \_senv_lines {^^J}}  % this is extremely slow because \seq_use:Nn is ⋆-expandable
	\str_gset:Nx #1 {\seq_map_function:NN \_senv_lines \_senv_append_newline:n }
}

\cs_generate_variant:Nn \saveenv_set_drop_last:Nn {NV}

\NewDocumentEnvironment {saveenvreinsert} {mm} {
	\saveenvkeeplastreinsert #1 {#2}
} {
	\endsaveenvkeeplastreinsert
	\saveenv_set_drop_last:NV #1 #1
}

\NewDocumentEnvironment {saveenv} {m} {
	\saveenvkeeplast #1
} {
	\endsaveenvkeeplast
	\saveenv_set_drop_last:NV #1 #1
}


}

\cs_generate_variant:Nn \msg_error:nnnnn {nnoVV}
