% \iffalse meta-comment
%
% File: ltx-talk-decode.dtx Copyright (C) 2023-2026 Joseph Wright
%
% It 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
%
%    https://www.latex-project.org/lppl.txt
%
% This file is part of the "ltx-talk bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% The released version of this bundle is available from CTAN.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/josephwright/ltx-talk
%
% for those people who are interested.
%
% -----------------------------------------------------------------------
%
%<*driver>
\documentclass{l3doc}
% Additional commands needed in this source
\NewDocumentCommand\email{m}{\href{mailto:#1}{\nolinkurl{#1}}}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% ^^A As we are dealing with a class, this has to be done manually
% \def\filedate{2026-08-23}
% \def\fileversion{v0.6.0}
%
% \title{^^A
%   \pkg{ltx-talk-decode} -- Decoding overlay specs^^A
%   \thanks{This file describes \fileversion,
%     last revised \filedate.}^^A
% }
%
% \author{^^A
%  Joseph Wright^^A
%  \thanks{^^A
%    E-mail: \email{joseph@texdev.net}^^A
%   }^^A
% }
%
% \date{Released \filedate}
%
% \maketitle
%
% \begin{documentation}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{ltx-talk-decode} implementation}
%
% Start the \pkg{DocStrip} guards.
%    \begin{macrocode}
%<*class>
%    \end{macrocode}
%
% Identify the internal prefix.
%    \begin{macrocode}
%<@@=talk>
%    \end{macrocode}
%
% \begin{variable}{\l_@@_decode_overlays_bool}
%   The result from decoding: are we on the current slide. This may well be
%   better handled by moving to a |TF| signature: to be explored.
%    \begin{macrocode}
\bool_new:N \l_@@_decode_overlays_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_decode_trailing_bool}
%   Indicates the current slide trailing after all of those in the overlay
%   specification: needed for \cs{temporal}.
%    \begin{macrocode}
\bool_new:N \l_@@_decode_trailing_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_pauses_int, \c@pauses, \thepauses}
%   The automatically-incremented value for the relative overlay value.
%    \begin{macrocode}
\int_new:N \g_@@_pauses_int
\cs_new_eq:NN \c@pauses \g_@@_pauses_int
\cs_new:Npn \thepauses { \@arabic \g_@@_pauses_int }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_decode_modes_bool}
%   Tracks whether at least one mode was given with no overlays: this means
%   that the specification selects only included modes.
%    \begin{macrocode}
\bool_new:N \l_@@_decode_modes_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_decode_step_bool}
%   Tracks whether to step \cs{g_@@_pauses_int}.
%    \begin{macrocode}
\bool_new:N \l_@@_decode_step_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_decode_arg_str}
%   For error usage.
%    \begin{macrocode}
\str_new:N \l_@@_decode_arg_str
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_decode_overlays_clist, \l_@@_decode_overlays_str}
%   The decoded overlay specification: will have only absolute slide numbers
%   present, potentially along with ranges.
%    \begin{macrocode}
\clist_new:N \l_@@_decode_overlays_clist
\str_new:N \l_@@_decode_overlays_str
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_decode_action_str}
%   The action which is active, if any.
%    \begin{macrocode}
\str_new:N \l_@@_decode_action_str
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}
%   {
%     \l_@@_decode_actions_bool  ,
%     \l_@@_decode_actions_clist ,
%     \l_@@_decode_actions_str
%   }
%   For the actions versions of overlay tracking.
%    \begin{macrocode}
\bool_new:N \l_@@_decode_actions_bool
\clist_new:N \l_@@_decode_actions_clist
\str_new:N \l_@@_decode_actions_str
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}
%   {\@@_decode_parse:n, \@@_decode_parse_auxi:n, \@@_decode_parse_auxii:n}
% \begin{macro}{\@@_decode_parse:w}
%   First a simple check for an entirely blank argument: if that's the case,
%   there is no additional overlay to consider. Then deal with any category
%   code issues before looping over blocks divided by \verb"|" tokens.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_decode_parse:n #1
  { \exp_args:Ne \@@_decode_parse_auxi:n {#1} }
\cs_new_protected:Npn \@@_decode_parse_auxi:n #1
  {
    \str_clear:N \l_@@_decode_action_str
    \bool_lazy_or:nnTF
      { \tl_if_blank_p:n {#1} }
      { \str_if_eq_p:nn {#1} { all } }
      { \bool_set_true:N \l_@@_decode_overlays_bool }
      {
        \str_set:Nn \l_@@_decode_arg_str {#1}
        \bool_set_false:N \l_@@_decode_actions_bool
        \bool_set_false:N \l_@@_decode_overlays_bool
        \bool_set_false:N \l_@@_decode_trailing_bool
        \bool_set_false:N \l_@@_decode_modes_bool
        \clist_clear:N \l_@@_decode_actions_clist
        \clist_clear:N \l_@@_decode_overlays_clist
        \exp_args:No \@@_decode_parse_auxii:n { \l_@@_decode_arg_str }
      }
  }
%    \end{macrocode}
%   Stepping the value assigned to |+| is done in the outer loop, as within one
%   overlay expression it always takes the same value. If the \pkg{amsmath}
%   \tn{ifmeasuring@} flag is on, the overlay counter is not advanced.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_decode_parse_auxii:n #1
  {
    \bool_set_false:N \l_@@_decode_step_bool
    \@@_decode_parse:w #1 | \q_recursion_tail | \q_recursion_stop
    \bool_if:NT \l_@@_decode_step_bool
      {
        \legacy_if:nF { measuring@ }
          { \int_gincr:N \g_@@_pauses_int }
      }
  }
%    \end{macrocode}
%   The end-of-loop test here covers the case where the active mode is not
%   mentioned at all in the specification.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_decode_parse:w #1 |
  {
    \quark_if_recursion_tail_stop_do:nn {#1}
      {
        \bool_lazy_and:nnT
          { \str_if_empty_p:N \l_@@_decode_overlays_str }
          { ! \l_@@_decode_modes_bool }
          { \bool_set_true:N \l_@@_decode_overlays_bool }
      }
    \exp_args:Ne \@@_decode_mode:n
      { \tl_trim_spaces:n {#1} }
    \@@_decode_parse:w 
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{variable}{\c_@@_modes_clist}
%   The possible modes: detokenized as that is applied up-front in decoding.
%    \begin{macrocode}
\clist_const:Ne \c_@@_modes_clist
  {
    \tl_to_str:n { article }   ,
    \tl_to_str:n { handout }   ,
    \tl_to_str:n { projector }
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_decode_mode:n}
% \begin{macro}{\@@_decode_mode:w}
% \begin{macro}{\@@_decode_mode_aux:n}
%   Check if the mode is known and current. If we find an action but have no
%   overlay details, they are filled in with a \verb|*|. Notice that if
%   we get a hit here for the mode test, there is no overlay part: we therefore
%   have a spec that excludes other modes. Also notice that if frame breaking
%   is active we only look for overall mode specifications, not any further
%   structure.
%    \begin{macrocode}
\cs_new_protected:Npe \@@_decode_mode:n #1
  {
    \clist_if_in:NnTF \exp_not:N \c_@@_modes_clist {#1}
      {
        \bool_set_true:N \exp_not:N \l_@@_decode_modes_bool
        \exp_not:N \str_if_eq:VnT
          \exp_not:N \l_@@_mode_str {#1}
          { \bool_set_true:N \exp_not:N \l_@@_decode_overlays_bool }
      }
      {
        \exp_not:N \bool_if:NF \exp_not:N \l_@@_frame_break_bool
          {
            \exp_not:N \@@_decode_mode:w #1 \tl_to_str:n { : : }
              \exp_not:N \q_stop
          }
      }
  }
\use:e
  {
    \cs_new_protected:Npe \exp_not:N \@@_decode_mode:w
      #1 \token_to_str:N :
      #2 \token_to_str:N : 
      #3 \exp_not:N \q_stop
  }
  {
    \exp_not:N \tl_if_blank:nTF {#2}
      {
        \exp_not:N \@@_decode_mode:nn
          { \tl_to_str:n { projector } } {#1}
      }
      { \exp_not:N \@@_decode_mode:nn {#1} {#2} }
  }
\cs_new_protected:Npn \@@_decode_mode:nn #1#2
  {
    \str_if_eq:VnTF \l_@@_mode_str {#1}
      {
        \@@_decode_action:n {#2}
        \str_if_empty:NT \l_@@_decode_overlays_str
          { \@@_decode_overlays:nn { overlays } { * } }
      }
      {
        \tl_if_blank:nT {#2}
          { \bool_set_true:N \l_@@_decode_modes_bool }
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_decode_action:n}
% \begin{macro}{\@@_decode_action:w}
%   Here, we have two valid possibilities: no action specification at all,
%   or from the known list. If we don't find one of those outcomes, we can
%   issue an error.
%    \begin{macrocode}
\cs_new_protected:Npe \@@_decode_action:n #1
  {
    \exp_not:N \@@_decode_action:w
      #1 \tl_to_str:n { @ @ } \exp_not:N \q_stop
  }
\use:e
  {
    \cs_new_protected:Npn \exp_not:N \@@_decode_action:w
      #1 \tl_to_str:n { @ } #2 \tl_to_str:n { @ } #3 \exp_not:N \q_stop
  }
  {
    \tl_if_blank:nTF {#2}
      {
        \str_if_empty:NTF \l_@@_decode_action_str
          { \@@_decode_overlays:nn { overlays } {#1} }
          {
            \msg_error:nnV { talk } { misplaced-action-spec }
              \l_@@_decode_arg_str
          }
      }
      {
        \cs_if_exist:cTF { @@_action_ #1 :N }
          {
            \str_if_empty:NTF \l_@@_decode_action_str
              {
                \str_set:Nn \l_@@_decode_action_str {#1}
                \tl_if_blank:nF {#2}
                  {
                    \@@_decode_overlays:nn { actions } {#2}
                    \clist_if_empty:NT \l_@@_decode_overlays_clist
                      { \bool_set_true:N \l_@@_decode_overlays_bool }
                  }
              }
              {
                \msg_error:nnV { talk } { duplicate-action-spec }
                  \l_@@_decode_arg_str
              }
          }
          {
            \msg_error:nnV { talk } { bad-action-spec }
              \l_@@_decode_arg_str
           }
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_decode_overlays:nn}
% \begin{macro}{\@@_decode_overlays:nN}
% \begin{macro}{\@_decode_overlay_+:nw, \@@_decode_overlay_.:nw}
% \begin{macro}{\@@_decode_overlay_aux:nNN}
% \begin{macro}{\@@_decode_overlay_offset:nNnN}
% \begin{macro}{\@@_decode_overlay_offset:nNn}
%   The loop here needs to replace all |+| and |.| characters by the current
%   automatic value, allowing for any offsets. Stepping the value assigned
%   here is done in the outer loop (see above).
%    \begin{macrocode}
\cs_new_protected:Npn \@@_decode_overlays:nn #1#2
  {
    \str_clear:c { l_@@_decode_ #1 _str }
    \@@_decode_overlays:nN {#1} #2 \q_recursion_tail \q_recursion_stop
    \@@_decode_check:n {#1}
  }
\cs_new_protected:Npn \@@_decode_overlays:nN #1#2
  {
    \quark_if_recursion_tail_stop:N #2
    \cs_if_exist_use:cF { @@_decode_overlay_ #2 :nw }
      {
        \str_put_right:cn { l_@@_decode_ #1 _str } {#2}
        \@@_decode_overlays:nN
      }
        {#1}
  }
\cs_new_protected:cpn { @@_decode_overlay_+:nw } #1
  {
    \bool_set_true:N \l_@@_decode_step_bool
    \@@_decode_overlay_aux:nNN {#1} 1
  }
\cs_new_protected:cpn { @@_decode_overlay_.:nw } #1 
  { \@@_decode_overlay_aux:nNN {#1} 0 }
%    \end{macrocode}
%   The look-ahead for an offset to a relative specification. If the
%   end-of-loop is reached, the value still needs to be inserted: to
%   share auxiliaries, that is done by using the same function as elsewhere,
%   so the end-of-loop markers are re-inserted. Otherwise, there is a check
%   to see if the next token is a |(|.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_decode_overlay_aux:nNN #1#2#3
  {
    \quark_if_recursion_tail_stop_do:Nn #3
      {
        \@@_decode_overlay_offset:nNn {#1} #2 { 0 }
        \q_recursion_tail \q_recursion_stop
      }
    \token_if_eq_meaning:NNTF #3 ( % )
      { \@@_decode_overlay_offset:nNnN {#1} #2 { } }
      { \@@_decode_overlay_offset:nNn {#1} #2 { 0 } #3 }
  }
%    \end{macrocode}
%   For the end of an offset, any valid overlay specification must have a
%   closing |)|, so this time the end-of-loop case is an error. Otherwise
%   simply collect up tokens until the closing |)| is found.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_decode_overlay_offset:nNnN #1#2#3#4
  {
    \quark_if_recursion_tail_stop_do:Nn #4
      {
        \msg_error:nnV { talk } { bad-action-spec }
          \l_@@_decode_arg_str
      } % (
    \token_if_eq_meaning:NNTF #4 )
      { \@@_decode_overlay_offset:nNn {#1} #2 {#3} }
      { \@@_decode_overlay_offset:nNnN {#1} #2 {#3#4} }
  }
%    \end{macrocode}
%   Overlay values can never be negative: this is enforced here.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_decode_overlay_offset:nNn #1#2#3
  {
    \str_put_right:ce { l_@@_decode_ #1 _str }
      { \int_max:nn { 0 } { #3 + \g_@@_pauses_int + #2 } }
    \@@_decode_overlays:nN {#1}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_decode_check:n}
% \begin{macro}{\@@_decode_check:nw}
% \begin{macro}{\@@_decode_check_single:nn}
% \begin{macro}{\@@_decode_check_range:nnn}
%   At this stage we have a fully \enquote{written out} overlay specification,
%   and need to work out if the current slide is included. We need to look at
%   each entry in the comma-separated list to sort this out. First we filter
%   out the case of a |*|, then it's a question of working out whether each
%   entry is a single number or a range, and if the latter, whether it's
%   open at either the start or the end.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_decode_check:n #1 
  {
    \clist_if_empty:cF { l_@@_decode_ #1 _clist }
      {
        \msg_error:nnV { talk } { bad-action-spec }
          \l_@@_decode_arg_str
      }
    \clist_set:cv { l_@@_decode_ #1 _clist } { l_@@_decode_ #1 _str }
    \clist_if_in:cnTF { l_@@_decode_ #1 _clist } { * }
      { \bool_set_true:c { l_@@_decode_ #1 _bool } }
      {
        \clist_map_inline:cn { l_@@_decode_ #1 _clist }
          { \@@_decode_check:nw {#1} 0 ##1 - - \q_stop }
      }
  }
%    \end{macrocode}
%   If |#4| is empty, both of the \enquote{filler} |-| tokens were consumed: we
%   have a single value. Otherwise there is a range: the setup above ensures
%   that there will be a starting value in all cases due to the leading |0|, but
%   there may not be an end one.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_decode_check:nw #1#2 - #3 - #4 \q_stop
  {
    \tl_if_empty:nTF {#4}
      { \@@_decode_check_single:nn {#1} {#2} }
      {
        \tl_if_blank:nTF {#3}
          { \@@_decode_check_range:nnn {#1} {#2} { \c_max_int } }
          { \@@_decode_check_range:nnn {#1} {#2} {#3} }
      }
  }
\cs_new_protected:Npn \@@_decode_check_single:nn #1#2
  {
    \int_compare:nNnTF \g_@@_slide_int = {#2}
      { \bool_set_true:c { l_@@_decode_ #1 _bool } }
      {
        \int_compare:nNnTF {#2} > \g_@@_slide_int
          { \bool_gset_true:N \g_@@_slide_continue_bool }
          { \bool_set_true:N \l_@@_decode_trailing_bool }
      }
  }
%    \end{macrocode}
%   TODO: In the following we might want to add a check whether the range was
%   given with |#2| being smaller than |#3|, to be decided upon.
%    \begin{macrocode}
\cs_set_protected:Npn \@@_decode_check_range:nnn #1#2#3
  {
    \int_compare:nNnTF \g_@@_slide_int > {#3}
      { \bool_set_true:N \l_@@_decode_trailing_bool }
      {
        \int_compare:nNnTF \g_@@_slide_int < {#2}
          { \bool_gset_true:N \g_@@_slide_continue_bool }
          {
            \bool_set_true:c { l_@@_decode_ #1 _bool }
            \bool_lazy_and:nnT
              { \int_compare_p:nNn \g_@@_slide_int < {#3} }
              { \int_compare_p:nNn {#3} < \c_max_int }
              { \bool_gset_true:N \g_@@_slide_continue_bool }
            \clist_map_break:
          }
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
%    \begin{macrocode}
\msg_new:nnnn { talk } { bad-action-spec }
  { Bad~overlay~specification~"#1". }
  {
    The~overlay~specification~given~doesn't~follow~the~pattern~described~in~
    the~ltx-talk~manual:~it~has~been~ignored.
  }
\msg_new:nnnn { talk } { duplicate-action-spec }
  { Duplicate~action~in~overlay~specification~"#1". }
  {
    The~overlay~specification~contains~more~than~one~action:
    ltx-talk~only~supports~a~single~action~in~one~overlay.
  }
\msg_new:nnnn { talk } { misplaced-action-spec }
  { Misplaced~action~in~overlay~specification~"#1". }
  {
    The~overlay~specification~contains~an~action~before~the~overlay~
    part(s):~this~is~not~supported.
  }
%    \end{macrocode}
%
%    \begin{macrocode}
%</class>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
