%%
%% Package: spectralsequences v1.3.3 2023-01-28
%% Author: Hood Chatham
%% Email: [email protected]
%% Date: 2023-01-28
%% License: Latex Project Public License
%%
%% File: sseqforeach.code.tex
%%
%%    Patches the \foreach command to obtain better error reporting
%%    Also defines some of my own looping commands, \Do, \DoUntilOutOfBounds, \DoUntilOutOfBoundsThenNMore
%%    All of the \foreach stuff we are modifying is defined by tikz in the file /pgf/utilities/pgffor.code.tex
%%

\def\sseq@for@vars{}
\def\sseq@for@savemacro#1{\sseq@d@addto@macro\sseq@for@vars{\sseq@for@vars@do#1}}
\def\sseq@for@savemacro@noslash#1{\sseq@d@addto@macro\sseq@for@vars{\sseq@for@vars@do@noslash#1}}
\def\sseq@for@vars@do#1{; \string#1 = #1}
\def\sseq@for@vars@do@noslash#1{; \sseq@macroname#1 = #1}

\ExplSyntaxOn
\def\sseq@for@printvars{\ifx\sseq@for@vars\pgfutil@empty\else\exp_last_unbraced:Nf\@gobble\sseq@for@vars\fi}

\newcount\sseq@Do@depth

\def\sseq@save@Do@var{
   \sseq@atbeginforeach@msgsetup
   \advance\sseq@Do@depth\@ne
   \exp_args:Nc \sseq@for@savemacro@noslash { iteration ~ \the \sseq@Do@depth }
   \def\iteration{0}
}

\def\sseq@stepiteration{
   \edef\iteration{\the\numexpr\iteration+1}
   \cs_set_eq:cN { iteration ~ \the \sseq@Do@depth } \iteration
}

\protected\def\sseq@Do{
   \begingroup
   \edef\sseq@inputline{\the\inputlineno}
   \sseq@callas{\Do}
   \sseq@call{\sseq@Do@}
}

\def\sseq@Do@#1#2{
   \sseq@esetthiscall{\string\Do\unexpanded{#1}}
   \sseq@opushstacktrace{\string\Do{#1}}
   \sseq@ifintexpr{#1}{
       \sseq@savestack
       \sseq@save@Do@var
       \prg_replicate:nn {#1} {
           \sseq@stepiteration
           #2
           \relax
       }
       \sseq@restorestack
   }{\sseq@error@xx{Do-invalid-int-expr}{\string\Do}{\unexpanded{#1}}}
   \endgroup
}

\protected\def\sseq@DoUntilOutOfBounds{
   \begingroup
   \edef\sseq@inputline{\the\inputlineno}
   \sseq@callas{\DoUntilOutOfBounds}
   \sseq@call{\sseq@DoUntilOutOfBounds@}
}

\def\sseq@DoUntilOutOfBounds@ #1 {
   \sseq@esetthiscall{\string\DoUntilOutOfBounds}
   \sseq@opushstacktrace{\string\DoUntilOutOfBounds}
   \ifx\sseq@xminmax\@gobbletwo\else\ifx\sseq@yminmax\@gobbletwo\else
       \sseq@error@x{DoUntil-no-bound}{\string\DoUntilOutOfBounds}
       \sseq@breakfifi
   \fi\fi
   \def\sseq@commandname{\string\DoUntilOutOfBounds}
   \sseq@savestack % so we can nest these
   \sseq@DoUntilOutOfBounds@body{#1}
   \sseq@restorestack
   \sseq@breakpoint
   \endgroup
}

\protected\def\sseq@DoUntilOutOfBoundsThenNMore{
   \begingroup
   \edef\sseq@inputline{\the\inputlineno}
   \sseq@callas{\DoUntilOutOfBoundsThenNMore}
   \sseq@call{\sseq@DoUntilOutOfBoundsThenNMore@}
}

\def\sseq@DoUntilOutOfBoundsThenNMore@ #1#2 {
   \sseq@esetthiscall{\string\DoUntilOutOfBoundsThenNMore\unexpanded{#1}}
   \sseq@opushstacktrace{\string\DoUntilOutOfBoundsThenNMore{#1}}
   \ifx\sseq@xminmax\@gobbletwo\else\ifx\sseq@yminmax\@gobbletwo\else
       \sseq@error@n{DoUntil-no-bound}{\DoUntilOutOfBoundsThenNMore}
       \sseq@breakfifi
   \fi\fi
   \sseq@ifintexpr{#1}{}{
       \sseq@error@xx{Do-invalid-int-expr}{\string\DoUntilOutOfBoundsThenNMore}{\unexpanded{#1}}
       \sseq@break
   }
   \def\sseq@commandname{\string\DoUntilOutOfBoundsThenNMore}
   \sseq@savestack
   \sseq@DoUntilOutOfBounds@body{#2}
   \prg_replicate:nn {#1} {
       \sseq@stepiteration
       #2
       \relax
   }
   \sseq@restorestack
   \sseq@breakpoint
   \endgroup
}

\def\sseq@DoUntilOutOfBounds@body#1{
   \sseq@save@Do@var % Set iteration so that \DoUntilOutOfBoundsThenNMore doesn't get upset if we're already out of bounds
   % If we're already out of bounds, we'll just do nothing.
   \sseq_if_out_of_bounds_noparse:nnTF{\lastx{0}}{\lasty{0}}{
       \bool_set_true:N \l_tmpa_bool
   }{
       \bool_set_false:N \l_tmpa_bool
       % Now we need to set up the loop descent condition.
       \sseq@tempxb=\lastx{0}\relax % Record current x and y values
       \sseq@tempyb=\lasty{0}\relax
       \sseq@stepiteration
       #1 % run loop body once
       \relax % protect from \d page grabber
       \sseq_if_out_of_bounds_noparse:nnTF{\lastx{0}}{\lasty{0}}{ % If we're out of bounds now, we can quit
           \bool_set_true:N \l_tmpa_bool
       }{ % Otherwise, determine descent check
           \sseq@tempx=\lastx{0}\relax % store new last value for comparison
           \sseq@tempy=\lasty{0}\relax
           \bool_set_false:N \l_tmpb_bool % This is to record whether there is a defined x range or y range. If neither, we'll throw an error.
%            \def\sseq@checkbound{\bool_set_false:N \l_tmpb_bool}
           \ifx\sseq@xminmax\@gobbletwo
               \ifnum\sseq@tempx>\sseq@tempxb
                   \bool_set_true:N \l_tmpb_bool % We have a descent condition
%                    \sseq@d@addto@macro\sseq@checkbound{
%                        \ifnum\sseq@tempx>\sseq@tempxb % Any stage of the loop is okay as long as it increases x
%                            \bool_set_true:N \l_tmpb_bool
%                        \fi
%                    }
               \else
                   \ifnum\sseq@tempx<\sseq@tempxb
                       \bool_set_true:N \l_tmpb_bool
%                        \sseq@d@addto@macro\sseq@checkbound{ % Any stage of the loop is okay as long as it decreases x
%                            \ifnum\sseq@tempx<\sseq@tempxb
%                                \bool_set_true:N \l_tmpb_bool
%                            \fi
%                        }
                   \fi
               \fi
           \fi
           \ifx\sseq@yminmax\@gobbletwo
               \ifnum\sseq@tempy>\sseq@tempyb
                   \bool_set_true:N \l_tmpb_bool
%                    \sseq@d@addto@macro\sseq@checkbound{
%                        \ifnum\sseq@tempy>\sseq@tempyb
%                            \bool_set_true:N \l_tmpb_bool
%                        \fi
%                    }
               \else
                   \ifnum\sseq@tempy<\sseq@tempyb
                       \bool_set_true:N \l_tmpb_bool
%                        \sseq@d@addto@macro\sseq@checkbound{
%                            \ifnum\sseq@tempy<\sseq@tempyb
%                                \bool_set_true:N \l_tmpb_bool
%                            \fi
%                        }
                   \fi
               \fi
           \fi
           \if_bool:N \l_tmpb_bool\else:
               \sseq@error@x{DoUntil-no-progress}{\sseq@commandname}
           \fi:
       }
   }
   \bool_until_do:Nn \l_tmpa_bool {
       \sseq@stepiteration
       #1
       \relax
       \sseq_if_out_of_bounds_noparse:nnTF{\lastx{0}}{\lasty{0}}{% we're done
           \bool_set_true:N \l_tmpa_bool
       }{% Check descent condition
%            \ifnum\iteration/10*10-\iteration=\z@
%                \sseq@tempx=\lastx{0}\relax % store new last value for comparison
%                \sseq@tempy=\lasty{0}\relax
%                \bool_set_false:N \l_tmpb_bool
%                \sseq@checkbound
%                \if_bool:N \l_tmpb_bool\else:
%                    \sseq@error{DoUntilOutOfBounds-descent-failed}
%                \fi:
%                \sseq@tempxb=\sseq@tempx
%                \sseq@tempyb=\sseq@tempx
%            \fi
       }
   }
}



\ExplSyntaxOff


\def\sseq@for@nopatch{\sseq@error{foreach-patch-failed}\def\sseq@patchfor{}\endinput}

\newtoks\sseq@foreachcall

\def\sseq@pgffor@atbeginforeach{%
   \begingroup %
   \sseq@atbeginforeach@msgsetup
   % \pgffor@macro@list calls \pgffor@normal@list, so we need to mark that the list has already been added to foreachcall.
   \sseq@tempiftrue
}


\def\sseq@patchfor{%
   \let\pgffor@atbeginforeach\sseq@pgffor@atbeginforeach
   \let\pgffor@@vars@opt\sseq@pgffor@@vars@opt
}
\def\sseq@pgffor@modify#1{%
   \@xp\let\csname sseq@\sseq@macroname#1\endcsname #1%
   \eappto\sseq@patchfor{\let\@nx#1 \@xp\@nx\csname sseq@\sseq@macroname#1\endcsname}%
}



\def\sseq@pgffor@recordarg#1#2{
   \sseq@pgffor@modify#1
   \@xp\pretocmd\csname sseq@\sseq@macroname#1\endcsname{\sseq@foreachcall\@xp{\the\sseq@foreachcall#2}}{}{\sseq@for@nopatch}
}
\def\sseq@pgffor@erecordarg#1#2{
   \sseq@pgffor@modify#1
   \@xp\pretocmd\csname sseq@\sseq@macroname#1\endcsname{\sseq@eval{\sseq@foreachcall{\the\sseq@foreachcall#2}}}{}{\sseq@for@nopatch}
}


\bgroup\lccode`\*=`\#\lowercase{\egroup

% Modify the foreach argument parser commands to put the call into \sseq@foreachcall and to tell us what the variables are
\def\sseq@pgffor@@vars@opt[#1]{\sseq@foreachcall\@xp{\the\sseq@foreachcall#1}\pgfkeys{/sseqpages/foreach/.cd,#1}\pgffor@vars}

\sseq@pgffor@recordarg\pgffor@@vars{*1}
\pretocmd\sseq@pgffor@@vars{\sseq@for@savemacro*1}{}{\sseq@for@nopatch}

\sseq@pgffor@recordarg\pgffor@@vars@slash@gobble{/}
\sseq@pgffor@recordarg\pgffor@macro@list{in *1}
\pretocmd\sseq@pgffor@macro@list{\sseq@tempiffalse}{}{\sseq@for@nopatch}% Don't add this again in  \pgffor@normal@lis

\sseq@pgffor@modify\pgffor@normal@list
% Add list to argument if it wasn't a macro
\pretocmd\sseq@pgffor@normal@list{\ifsseq@tempif\sseq@foreachcall\@xp{\the\sseq@foreachcall in {*1}}\fi\sseq@tempiftrue}{}{\sseq@for@nopatch}

\sseq@pgffor@recordarg\pgffor@collectforeach@macro{\foreach}
\sseq@pgffor@recordarg\pgffor@collectforeach@normal{\foreach}

\sseq@pgffor@modify\pgffor@iterate
\pretocmd\sseq@pgffor@iterate{\sseq@opushstacktrace{\the\sseq@foreachcall}\sseq@thiscalltoks\@xp{\the\sseq@foreachcall}}{}{\sseq@for@nopatch}

\sseq@pgffor@modify\pgffor@doloop
\sseq@pgffor@modify\pgffor@invokebody
\patchcmd\sseq@pgffor@doloop{\pgffor@begingroup}{\pgffor@begingroup\sseq@xsetlastcall{\the\sseq@foreachcall}}{}{\sseq@for@nopatch}
\patchcmd\sseq@pgffor@invokebody{\pgffor@begingroup}{\pgffor@begingroup\sseq@xsetlastcall{\the\sseq@foreachcall}}{}{\sseq@for@nopatch}
}

%TODO: also hook \pgffor@assign@parse, \pgffor@remember@parse, \pgffor@count@parse.