% \iffalse meta-comment
% An Infrastructure for Semantic Macros and Module Scoping
% Copyright (c) 2019 Michael Kohlhase, all rights reserved
%                this file is released under the
%                LaTeX Project Public License (LPPL)
% 
% The original of this file is in the public repository at 
% http://github.com/sLaTeX/sTeX/
%
% TODO update copyright  
%
%<*driver>
\providecommand\bibfolder{../../lib/bib}
\input{../../doc/stex-docheader}

\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{ \sTeX-Module Inheritance
% 	\thanks{Version {\fileversion} (last revised {\filedate})} 
% }
%
% \author{Michael Kohlhase, Dennis Müller\\
% 	FAU Erlangen-Nürnberg\\
% 	\url{http://kwarc.info/}
% }
%
% \maketitle
%
%\ifinfulldoc\else
% This is the documentation for the \pkg{stex-inheritance} package.
% For a more high-level introduction, 
%  see \href{\basedocurl/manual.pdf}{the \sTeX Manual} or the
% \href{\basedocurl/stex.pdf}{full \sTeX documentation}.
% \fi
%
% \begin{documentation}\label{pkg:inheritance:doc}
%
% Code related to Module Inheritance, in particular \emph{sms mode}.
%
%\ifinfulldoc\else
% \input{../../doc/packages/stex-inheritance}
%\fi
%
% \section{Macros and Environments}\label{pkg:inheritance:doc:macros}
%
%    \subsection{SMS Mode}
% ``SMS Mode'' is used when loading modules from external tex files.
% It deactivates any output and ignores all \TeX\ commands
% not explicitly allowed via the following lists -- all of which either
% declare module content or are needed in order to declare module content:
%
% \begin{variable}{\g_stex_smsmode_allowedmacros_tl}
%   Macros that are executed as is; i.e. sms mode continues immediately after.
%   These macros may not take any arguments or otherwise gobble tokens.
%
%   Initially: \cs{makeatletter}, \cs{makeatother}, \cs{ExplSyntaxOn},
%   \cs{ExplSyntaxOff}.
% \end{variable}
%
% \begin{variable}{\g_stex_smsmode_allowedmacros_escape_tl}
%   Macros that are executed and potentially gobble up further tokens.
%   These macros need to make sure, that the very last token they ultimately
%   expand to is \cs{stex_smsmode_do:}.
%
%   Initially: \cs{symdecl}, \cs{notation}, \cs{symdef}, 
%   \cs{importmodule}, \cs{STEXexport}, \cs{inlineass}, \cs{inlinedef},
%   \cs{inlineex}, \cs{endinput}, \cs{setnotation}, \cs{copynotation}.
% \end{variable}
%
% \begin{variable}{\g_stex_smsmode_allowedenvs_seq}
%   The names of environments that should be allowed in SMS mode.
%   The corresponding \cs{begin}-statements are treated like
%   the macros in \cs{g_stex_smsmode_allowedmacros_escape_tl}, so
%   \cs{stex_smsmode_do:} needs to be the last token in the
%   \cs{begin}-code. Since \cs{end}-statements take no arguments anyway,
%   those are called directly and sms mode continues afterwards.
%
%   Initially: |smodule|, |copymodule|, |interpretmodule|, |sdefinition|,
%   |sexample|, |sassertion|, |sparagraph|.
% \end{variable}
%
% \begin{function}[pTF]{\stex_if_smsmode:}
%   Tests whether SMS mode is currently active.
% \end{function}
%
% \begin{function}{\stex_file_in_smsmode:nn}
%   \begin{syntax} \cs{stex_in_smsmode:nn} \Arg{filename} \Arg{code} \end{syntax}
%   Executes \meta{code} in SMS mode, followed by the content of \meta{filename}. 
%   \meta{code} can be used e.g. to set the current repository, and is
%   executed within a new tex group, and the same group as the file content.
% \end{function}
%
% \begin{function}{\stex_smsmode_do:}
%   Starts gobbling tokens until one is encountered that is allowed in SMS mode.
% \end{function}
%
%    \subsection{Imports and Inheritance}
%
% \begin{function}{\importmodule}
%   \begin{syntax} \cs{importmodule}|[|\meta{archive-ID}|]|\Arg{module-path} \end{syntax}
%   Imports a module by reading it from a file and ``activating'' it.
%   \sTeX determines the module and its containing file by passing its
%   arguments on to \cs{stex_import_module_path:nn}.
%   
% \end{function}
%
% \begin{function}{\usemodule}
%   \begin{syntax} \cs{importmodule}|[|\meta{archive-ID}|]|\Arg{module-path} \end{syntax}
%   Like \cs{importmodule}, but does not export its contents;
%   i.e. including the current module will not activate the used module
% \end{function}
%
% \begin{function}{\stex_import_module_uri:nn}
%   \begin{syntax} \cs{stex_import_module_uri:nn} \Arg{archive-ID} \Arg{module-path} \end{syntax}
%   Determines the URI of a module by splitting
%   \meta{module-path} into \meta{path}|?|\meta{name}. If \meta{module-path}
%   does \emph{not} contain a |?|-character, we consider it to be the \meta{name},
%   and \meta{path} to be empty.
%
%   If \meta{archive-ID} is empty, it is automatically set to the
%   ID of the current archive (if one exists).
%
%   \begin{enumerate}
%   \item If \meta{archive-ID} is empty:
%     \begin{enumerate}
%     \item If \meta{path} is empty, then
%         \meta{name} must have been declared earlier in the same file
%         and retrievable from \cs{g_stex_modules_in_file_seq}, or
%         a file with name \meta{name}|.|\meta{lang}|.tex| must exist
%         in the same folder, containing a module \meta{name}.
%
%         That module should have the same namespace as the current one.
%     \item If \meta{path} is not empty, it must point to the relative
%         path of the containing file as well as the namespace.
%     \end{enumerate}
%   \item Otherwise:
%      \begin{enumerate}
%         \item If \meta{path} is empty, then
%         \meta{name} must have been declared earlier in the same file
%         and retrievable from \cs{g_stex_modules_in_file_seq}, or
%         a file with name \meta{name}|.|\meta{lang}|.tex| must exist
%         in the top |source| folder of the archive, 
%         containing a module \meta{name}.
%
%         That module should lie directly in the namespace 
%         of the archive.
%     \item If \meta{path} is not empty, it must point to the
%         path of the containing file as well as the namespace,
%         relative to the namespace of the archive.
%
%         If a module by that namespace exists, it is returned.
%         Otherwise, we call \cs{stex_require_module:nn} 
%         on the |source| directory of the archive to find the
%         file.
%       \end{enumerate}
%   \end{enumerate}
% \end{function}
% \begin{variable}{
%   \l_stex_import_name_str,\l_stex_import_archive_str,\l_stex_import_path_str,\l_stex_import_ns_str
% }
%  stores the result in these four variables.
% \end{variable}
%
% \begin{function}{\stex_import_require_module:nnnn}
%    \begin{syntax} \Arg{ns} \Arg{archive-ID} \Arg{path} \Arg{name} \end{syntax}
%     Checks whether a module with URI \meta{ns}|?|\meta{name} already
%     exists. If not, it looks for a plausible file that declares
%     a module with that URI.
%
%     Finally, activates that module by executing its |_code|-macro.
% \end{function}
%
% \end{documentation}
%
% \begin{implementation}\label{pkg:inheritance:impl}
%
% \section{\sTeX-Module Inheritance Implementation}
%
%    \begin{macrocode}
%<*package>

%%%%%%%%%%%%%   inheritance.dtx   %%%%%%%%%%%%%

%    \end{macrocode}
%
% \subsection{SMS Mode}
%    \begin{macrocode}
%<@@=stex_smsmode>
%    \end{macrocode}
%
% \begin{variable}{
%   \g_stex_smsmode_allowedmacros_tl,
%   \g_stex_smsmode_allowedmacros_escape_tl,
%   \g_stex_smsmode_allowedenvs_seq
% }
%    \begin{macrocode}
\tl_new:N \g_stex_smsmode_allowedmacros_tl
\tl_new:N \g_stex_smsmode_allowedmacros_escape_tl
\seq_new:N \g_stex_smsmode_allowedenvs_seq

\tl_set:Nn \g_stex_smsmode_allowedmacros_tl {
  \makeatletter
  \makeatother
  \ExplSyntaxOn
  \ExplSyntaxOff
  \rustexBREAK
}

\tl_set:Nn \g_stex_smsmode_allowedmacros_escape_tl {
  \symdef
  \importmodule
  \notation
  \symdecl
  \STEXexport
  \inlineass
  \inlinedef
  \inlineex
  \endinput
  \setnotation
  \copynotation
}

\exp_args:NNx \seq_set_from_clist:Nn \g_stex_smsmode_allowedenvs_seq {
  \tl_to_str:n {
    smodule,
    copymodule,
    interpretmodule,
    sdefinition,
    sexample,
    sassertion,
    sparagraph
  }
}
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[pTF]{\stex_if_smsmode:}
%    \begin{macrocode}
\bool_new:N \g_@@_bool
\bool_set_false:N \g_@@_bool
\prg_new_conditional:Nnn \stex_if_smsmode: { p, T, F, TF } {
  \bool_if:NTF \g_@@_bool \prg_return_true: \prg_return_false:
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\_@@_in_smsmode:nn}
%    \begin{macrocode}
\cs_new_protected:Nn \_@@_in_smsmode:nn {
  \vbox_set:Nn \l_tmpa_box {
    \bool_set_eq:cN { l_@@_#1_bool } \g_@@_bool
    \bool_gset_true:N \g_@@_bool
    #2
    \bool_gset_eq:Nc \g_@@_bool { l_@@_#1_bool }
  }
  \box_clear:N \l_tmpa_box
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\stex_file_in_smsmode:nn}
%    \begin{macrocode}
\quark_new:N \q_@@_break

\cs_new_protected:Nn \stex_file_in_smsmode:nn {
  \stex_filestack_push:n{#1}
  \_@@_in_smsmode:nn{#1} {
    #2
    \everyeof{\q_@@_break\noexpand}
    \expandafter\expandafter\expandafter
    \stex_smsmode_do:
    \csname @ @ input\endcsname "#1"\relax
  }
  \stex_filestack_pop:
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\stex_smsmode_do:}
% is executed on encountering |\| in smsmode.
% It checks whether the corresponding command is allowed and executes
% or ignores it accordingly:
%    \begin{macrocode}
\cs_new_protected:Npn \stex_smsmode_do: {
  \stex_if_smsmode:T {
    \_@@_do:w
  }
}
\cs_new_protected:Npn \_@@_do:w #1 {
  \exp_args:Nx \tl_if_empty:nTF { \tl_tail:n{ #1 }}{
    \expandafter\if\expandafter\relax\noexpand#1
      \expandafter\_@@_do_aux:N\expandafter#1
    \else\expandafter\_@@_do:w\fi
  }{
    \_@@_do:w %#1
  }
}
\cs_new_protected:Nn \_@@_do_aux:N {
  \cs_if_eq:NNF #1 \q_@@_break {
    \tl_if_in:NnTF \g_stex_smsmode_allowedmacros_tl {#1} {
      #1\_@@_do:w
    }{
      \tl_if_in:NnTF \g_stex_smsmode_allowedmacros_escape_tl {#1} {
        #1
      }{
        \cs_if_eq:NNTF \begin #1 {
          \_@@_check_begin:n
        }{
          \cs_if_eq:NNTF \end #1 {
            \_@@_check_end:n
          }{
            \_@@_do:w
          }
        }
      }
    }
  }
}

\cs_new_protected:Nn \_@@_check_begin:n {
  \seq_if_in:NxTF \g_stex_smsmode_allowedenvs_seq { \detokenize{#1} }{
    \begin{#1}
  }{
    \_@@_do:w
  }
}
\cs_new_protected:Nn \_@@_check_end:n {
  \seq_if_in:NxTF \g_stex_smsmode_allowedenvs_seq { \detokenize{#1} }{
    \end{#1}\_@@_do:w
  }{
    \str_if_eq:nnTF{#1}{document}{\endinput}{\_@@_do:w}
  }
}
%    \end{macrocode}
% \end{macro}
%

% \subsection{Inheritance}
%    \begin{macrocode}
%<@@=stex_importmodule>
%    \end{macrocode}
%
% \begin{macro}{\stex_import_module_uri:nn}
%    \begin{macrocode}
\cs_new_protected:Nn \stex_import_module_uri:nn {
  \str_set:Nx \l_stex_import_archive_str { #1 }
  \str_set:Nn \l_stex_import_path_str { #2 }

  \exp_args:NNNo \seq_set_split:Nnn \l_tmpb_seq ? { \l_stex_import_path_str }
  \seq_pop_right:NN \l_tmpb_seq \l_stex_import_name_str
  \str_set:Nx \l_stex_import_path_str { \seq_use:Nn \l_tmpb_seq ? }

  \stex_modules_current_namespace:
  \bool_lazy_all:nTF {
    {\str_if_empty_p:N \l_stex_import_archive_str}
    {\str_if_empty_p:N \l_stex_import_path_str}
    {\stex_if_module_exists_p:n { \l_stex_module_ns_str ? \l_stex_import_name_str } }
  }{
    \str_set_eq:NN \l_stex_import_path_str \l_stex_modules_subpath_str
    \str_set_eq:NN \l_stex_import_ns_str \l_stex_module_ns_str
  }{
    \str_if_empty:NT \l_stex_import_archive_str {
      \prop_if_exist:NT \l_stex_current_repository_prop {
        \prop_get:NnN \l_stex_current_repository_prop { id } \l_stex_import_archive_str
      }
    }  
    \str_if_empty:NTF \l_stex_import_archive_str {
      \str_if_empty:NF \l_stex_import_path_str {
        \str_set:Nx \l_stex_import_ns_str {
          \l_stex_module_ns_str / \l_stex_import_path_str
        }
      }
    }{
      \stex_require_repository:n \l_stex_import_archive_str
      \prop_get:cnN { c_stex_mathhub_\l_stex_import_archive_str _manifest_prop } { ns }
        \l_stex_import_ns_str
      \str_if_empty:NF \l_stex_import_path_str {
        \str_set:Nx \l_stex_import_ns_str {
          \l_stex_import_ns_str / \l_stex_import_path_str
        }
      }
    }
  }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{
%   \l_stex_import_name_str,\l_stex_import_archive_str,\l_stex_import_path_str,\l_stex_import_ns_str
% }
%   Store the return values of \cs{stex_import_module_uri:nn}.
%    \begin{macrocode}
\str_new:N \l_stex_import_name_str
\str_new:N \l_stex_import_archive_str
\str_new:N \l_stex_import_path_str
\str_new:N \l_stex_import_ns_str
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\stex_import_require_module:nnnn}
%    \begin{syntax} \Arg{ns} \Arg{archive-ID} \Arg{path} \Arg{name} \end{syntax}
%    \begin{macrocode}
\cs_new_protected:Nn \stex_import_require_module:nnnn {
  \exp_args:Nx \stex_if_module_exists:nF { #1 ? #4 } {

    % archive
    \str_set:Nx \l_tmpa_str { #2 }
    \str_if_empty:NTF \l_tmpa_str {
      \seq_set_eq:NN \l_tmpa_seq \g_stex_currentfile_seq
    } {
      \stex_path_from_string:Nn \l_tmpb_seq { \l_tmpa_str }
      \seq_concat:NNN \l_tmpa_seq \c_stex_mathhub_seq \l_tmpb_seq
      \seq_put_right:Nn \l_tmpa_seq { source }
    }

    % path
    \str_set:Nx \l_tmpb_str { #3 }
    \str_if_empty:NTF \l_tmpb_str {
      \str_set:Nx \l_tmpa_str { \stex_path_to_string:N \l_tmpa_seq / #4 }
      
      \ltx@ifpackageloaded{babel} { 
        \exp_args:NNx \prop_get:NnNF \c_stex_language_abbrevs_prop 
            { \languagename } \l_tmpb_str {
              \msg_error:nnx{stex}{error/unknownlanguage}{\languagename}
            }
      } {
        \str_clear:N \l_tmpb_str
      }

      \stex_debug:nn{modules}{Checking~\l_tmpa_str.\l_tmpb_str.tex}
      \IfFileExists{ \l_tmpa_str.\l_tmpb_str.tex }{
        \str_gset:Nx \g_@@_file_str { \l_tmpa_str.\l_tmpb_str.tex }
      }{
        \stex_debug:nn{modules}{Checking~\l_tmpa_str.tex}
        \IfFileExists{ \l_tmpa_str.tex }{
          \str_gset:Nx \g_@@_file_str { \l_tmpa_str.tex }
        }{
          % try english as default
          \stex_debug:nn{modules}{Checking~\l_tmpa_str.en.tex}
          \IfFileExists{ \l_tmpa_str.en.tex }{
            \str_gset:Nx \g_@@_file_str { \l_tmpa_str.en.tex }
          }{
            \msg_error:nnx{stex}{error/unknownmodule}{#1?#4}
          }
        }
      }

    } {
      \seq_set_split:NnV \l_tmpb_seq / \l_tmpb_str
      \seq_concat:NNN \l_tmpa_seq \l_tmpa_seq \l_tmpb_seq
      
      \ltx@ifpackageloaded{babel} { 
        \exp_args:NNx \prop_get:NnNF \c_stex_language_abbrevs_prop 
            { \languagename } \l_tmpb_str {
              \msg_error:nnx{stex}{error/unknownlanguage}{\languagename}
            }
      } {
        \str_clear:N \l_tmpb_str
      }

      \stex_path_to_string:NN \l_tmpa_seq \l_tmpa_str

      \stex_debug:nn{modules}{Checking~\l_tmpa_str/#4.\l_tmpb_str.tex}
      \IfFileExists{ \l_tmpa_str/#4.\l_tmpb_str.tex }{
        \str_gset:Nx \g_@@_file_str { \l_tmpa_str/#4.\l_tmpb_str.tex }
      }{
        \stex_debug:nn{modules}{Checking~\l_tmpa_str/#4.tex}
        \IfFileExists{ \l_tmpa_str/#4.tex }{
          \str_gset:Nx \g_@@_file_str { \l_tmpa_str/#4.tex }
        }{
          % try english as default
          \stex_debug:nn{modules}{Checking~\l_tmpa_str/#4.en.tex}
          \IfFileExists{ \l_tmpa_str/#4.en.tex }{
            \str_gset:Nx \g_@@_file_str { \l_tmpa_str/#4.en.tex }
          }{
            \stex_debug:nn{modules}{Checking~\l_tmpa_str.\l_tmpb_str.tex}
            \IfFileExists{ \l_tmpa_str.\l_tmpb_str.tex }{
              \str_gset:Nx \g_@@_file_str { \l_tmpa_str.\l_tmpb_str.tex }
            }{
              \stex_debug:nn{modules}{Checking~\l_tmpa_str.tex}
              \IfFileExists{ \l_tmpa_str.tex }{
                \str_gset:Nx \g_@@_file_str { \l_tmpa_str.tex }
              }{
                % try english as default
                \stex_debug:nn{modules}{Checking~\l_tmpa_str.en.tex}
                \IfFileExists{ \l_tmpa_str.en.tex }{
                  \str_gset:Nx \g_@@_file_str { \l_tmpa_str.en.tex }
                }{
                  \msg_error:nnx{stex}{error/unknownmodule}{#1?#4}
                }
              }
            }
          }
        }
      }
    }

    \exp_args:No \stex_file_in_smsmode:nn { \g_@@_file_str } {
      \seq_clear:N \l_stex_all_modules_seq
      \str_clear:N \l_stex_current_module_str
      \str_set:Nx \l_tmpb_str { #2 }
      \str_if_empty:NF \l_tmpb_str {
        \stex_set_current_repository:n { #2 }
      }
      \stex_debug:nn{modules}{Loading~\g_@@_file_str}
    }
    
    \stex_if_module_exists:nF { #1 ? #4 } {
      \msg_error:nnx{stex}{error/unknownmodule}{
        #1?#4~(in~file~\g_@@_file_str)
      }
    }
  }
  \stex_activate_module:n { #1 ? #4 }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\importmodule}
%    \begin{macrocode}
\NewDocumentCommand \importmodule { O{} m } {
  \stex_import_module_uri:nn { #1 } { #2 }
  \stex_debug:nn{modules}{Importing~module:~
    \l_stex_import_ns_str ? \l_stex_import_name_str
  }
  \stex_if_smsmode:F {
    \stex_import_require_module:nnnn 
    { \l_stex_import_ns_str } { \l_stex_import_archive_str } 
    { \l_stex_import_path_str } { \l_stex_import_name_str }
    \stex_annotate_invisible:nnn 
      {import} {\l_stex_import_ns_str ? \l_stex_import_name_str} {}
  }
  \exp_args:Nx \stex_add_to_current_module:n {
    \stex_import_require_module:nnnn 
    { \l_stex_import_ns_str } { \l_stex_import_archive_str } 
    { \l_stex_import_path_str } { \l_stex_import_name_str }
  }
  \exp_args:Nx \stex_add_import_to_current_module:n {
    \l_stex_import_ns_str ? \l_stex_import_name_str
  }
  \stex_smsmode_do:
  \ignorespacesandpars
}
\stex_deactivate_macro:Nn \importmodule {module~environments}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\usemodule}
%    \begin{macrocode}
\NewDocumentCommand \usemodule { O{} m } {
  \stex_if_smsmode:F {
    \stex_import_module_uri:nn { #1 } { #2 }
    \stex_import_require_module:nnnn 
    { \l_stex_import_ns_str } { \l_stex_import_archive_str }
    { \l_stex_import_path_str } { \l_stex_import_name_str }
    \stex_annotate_invisible:nnn 
      {usemodule} {\l_stex_import_ns_str ? \l_stex_import_name_str} {}
  }
  \stex_smsmode_do:
  \ignorespacesandpars
}
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
