% \iffalse meta-comment
%
%% File: l3bitset.dtx
%
% Copyright (C) 2020-2025 The LaTeX Project
%
% 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
%
%    http://www.latex-project.org/lppl.txt
%
% This file is part of the "l3kernel bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver>
\documentclass[full,kernel]{l3doc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
% \title{^^A
%   The \pkg{l3bitset} module\\ Bitsets^^A
% }
%
% \author{^^A
%  The \LaTeX{} Project\thanks
%    {^^A
%      E-mail:
%        \href{mailto:latex-team@latex-project.org}
%          {latex-team@latex-project.org}^^A
%    }^^A
% }
%
% \date{Released 2025-12-24}
%
% \maketitle
%
% \begin{documentation}
%
% This module defines and implements the data type \texttt{bitset}, a vector of
% bits. The size of the vector may grow dynamically.
% Individual bits can be set and unset by names pointing to an index position.
% The names |1|, |2|, |3|, \ldots\ are predeclared and point to the
% index positions  $1$, $2$, $3$,\ldots. More names can be added and existing names can
% be changed.
% The index is like all other indices in \pkg{expl3} modules \emph{1-based}.
% A \texttt{bitset} can be output as binary number or---as needed e.g. in a
% PDF dictionary---as decimal (arabic) number.
% Currently only a small subset of the functions provided by the \pkg{bitset}
% package are implemented here, mainly the functions needed to use bitsets in
% PDF dictionaries.
%
% The bitset is stored as a string (but one shouldn't rely on the internal
% representation) and so the vector size is theoretically
% unlimited, only restricted by \TeX-memory. But the functions to set and clear
% bits use integer functions for the index so bitsets can't be longer
% than $2^{31} - 1$.
% The export function
% \cs{bitset_to_arabic:N} can use functions from the \texttt{int} module only if
% the largest index used for this bitset is smaller than $32$, for longer
% bitsets \texttt{fp} is used and this is slower.
%
% \section{Creating bitsets}
%
% \begin{function}[added = 2023-11-15]
%   {\bitset_new:N,\bitset_new:c,\bitset_new:Nn, \bitset_new:cn}
%   \begin{syntax}
%     \cs{bitset_new:N}  \meta{bitset var} \\
%     \cs{bitset_new:Nn} \meta{bitset var}
%     ~~\{
%     ~~~~\meta{name_1} |=| \meta{index_1} |,|
%     ~~~~\meta{name_2} |=| \meta{index_2} |,| \ldots{}
%     ~~\}
%   \end{syntax}
% Creates a new \meta{bitset var} or raises an error if the name is already taken.
% The declaration is global. The \meta{bitset var} is initially $0$.
%
% Bitsets are implemented as string variables consisting of
% \texttt{1}'s and \texttt{0}'s.
% The rightmost number is the index position $1$, so
% the string variable can be viewed directly as the binary number.
% But one shouldn't rely on the internal representation, but use the
% dedicated \cs{bitset_to_bin:N} instead to get the binary number.
%
% The name--index pairs given in the second
% argument of \cs{bitset_new:Nn} declares names for some indices,
% which can be used to set and unset bits.
% The names |1|, |2|, |3|, \ldots\ are predeclared and point to the
% index positions $1$, $2$, $3$, \ldots.
%
% \meta{index\ldots} should be a positive number or an
% \meta{integer expression} which evaluates to a positive number.
% The expression is evaluated when the index is used, not at declaration time.
% The names \meta{name\ldots}
% should be unique. Using a number as name, e.g.~|10=1|, is allowed, it
% then overwrites the predeclared name |10|,
% but the index position $10$ can then only be reached if some other
% name for it exists, e.g. |ten=10|.
% It is not necessary to give every index
% a name, and an index can have more than one name. The named index
% can be extended or changed with the next function.
% \end{function}
%
% \begin{function}[added = 2023-11-15]
%   {\bitset_addto_named_index:Nn}
%   \begin{syntax}
%     \cs{bitset_addto_named_index:Nn} \meta{bitset var}
%     ~~\{
%     ~~~~\meta{name_1} |=| \meta{index_1} |,|
%     ~~~~\meta{name_2} |=| \meta{index_2} |,| \ldots{}
%     ~~\}
%   \end{syntax}
%   This extends or changes the name--index pairs for \meta{bitset var}
%   globally as described for \cs{bitset_new:Nn}.
% \end{function}
%
% For example after these settings
% \begin{verbatim}
%   \bitset_new:Nn \l_pdfannot_F_bitset
%     {
%       Invisible      = 1,
%       Hidden         = 2,
%       Print          = 3,
%       NoZoom         = 4,
%       NoRotate       = 5,
%       NoView         = 6,
%       ReadOnly       = 7,
%       Locked         = 8,
%       ToggleNoView   = 9,
%       LockedContents = 10
%     }
%   \bitset_addto_named_index:Nn \l_pdfannot_F_bitset
%     {
%       print = 3
%     }
% \end{verbatim}
% it is possible to set bit $3$ by using any of these alternatives:
% \begin{verbatim}
%   \bitset_set_true:Nn \l_pdfannot_F_bitset {Print}
%   \bitset_set_true:Nn \l_pdfannot_F_bitset {print}
%   \bitset_set_true:Nn \l_pdfannot_F_bitset {3}
% \end{verbatim}
%
% \begin{function}[EXP, pTF,added = 2023-11-15]
%   {\bitset_if_exist:N, \bitset_if_exist:c}
%   \begin{syntax}
%     \cs{bitset_if_exist_p:N} \meta{bitset var}
%     \cs{bitset_if_exist:NTF} \meta{bitset var} \Arg{true code} \Arg{false code}
%   \end{syntax}
%   Tests whether the \meta{bitset var} exist.
% \end{function}
%
% \section{Setting and unsetting bits}
%
% \begin{function}[added = 2023-11-15]
%   {
%     \bitset_set_true:Nn, \bitset_set_true:cn,
%     \bitset_gset_true:Nn, \bitset_gset_true:cn
%   }
%   \begin{syntax}
%     \cs{bitset_set_true:Nn} \meta{bitset var} \Arg{name}
%   \end{syntax}
%   This sets the bit of the index position represented by \Arg{name} to $1$.
%   \meta{name} should be either one of the predeclared names
%   |1|, |2|, |3|, \ldots, or one of the names added manually.
%   Index position are 1-based.
%   If needed the length of the bit vector is enlarged.
% \end{function}
%
% \begin{function}[added = 2023-11-15]
%   {
%     \bitset_set_false:Nn, \bitset_set_false:cn,
%     \bitset_gset_false:Nn, \bitset_gset_false:cn
%   }
%   \begin{syntax}
%     \cs{bitset_set_false:Nn} \meta{bitset var} \Arg{name}
%   \end{syntax}
%   This unsets the bit of the index position represented by \Arg{name} (sets
%   it to $0$).
%   \meta{name} should be either one of the predeclared names
%   |1|, |2|, |3|, \ldots, or one of the names added manually.
%   The index is $1$-based. If the index position is larger
%   than the current length of the bit vector
%   nothing happens. If the leading (left most) bit is unset,
%   zeros are not trimmed but stay in the bit vector and are still shown
%   by \cs{bitset_show:N}.
% \end{function}
%
% \begin{function}[added = 2023-11-15]
%   {\bitset_clear:N,\bitset_clear:c,\bitset_gclear:N,\bitset_gclear:c}
%   \begin{syntax}
%     \cs{bitset_clear:N} \meta{bitset var}
%   \end{syntax}
%   This resets the bitset to the initial state. The declared names are not changed.
% \end{function}
%
% \section{Using bitsets}
%
% \begin{function}[EXP,added = 2023-11-15]
%   {\bitset_item:Nn, \bitset_item:cn}
%   \begin{syntax}
%     \cs{bitset_item:Nn} \meta{bitset var} \Arg{name}
%   \end{syntax}
% \cs{bitset_item:Nn} outputs \texttt{1} if the bit with
% the index number represented by \meta{name} is set and \texttt{0} otherwise.
%  \meta{name} is either one of the predeclared names
% |1|, |2|, |3|, \ldots, or one of the names added manually.
% \end{function}
%
% \begin{function}[EXP,added = 2023-11-15]
%   {\bitset_to_bin:N, \bitset_to_bin:c}
%   \begin{syntax}
%     \cs{bitset_to_bin:N} \meta{bitset var}
%   \end{syntax}
%   This leaves the current value of the bitset expressed as
%   a binary (string) number in the input stream.
%   If no bit has been set yet, the output is zero.
% \end{function}
%
% \begin{function}[EXP,added = 2023-11-15]
%   {\bitset_to_arabic:N, \bitset_to_arabic:c}
%   \begin{syntax}
%     \cs{bitset_to_arabic:N} \meta{bitset var}
%   \end{syntax}
%   This leaves the current value of the bitset expressed as
%   a decimal number in the input stream. If no bit has been set yet,
%   the output is zero. The function uses \cs{int_from_bin:n} if the largest
%   index that have been set or unset is smaller than $32$, and a slower
%   implementation based on \cs{fp_eval:n} otherwise.
% \end{function}
%
% \begin{function}[EXP, added = 2024-11-12]{\bitset_use:N, \bitset_use:c}
%   \begin{syntax}
%     \cs{bitset_use:N} \meta{bitset var}
%   \end{syntax}
%   This leaves the current value of the bitset expressed as
%   a binary (string) number in the input stream.
%   If no bit has been set yet, the output is zero. This is
%   functionally equivalent to \cs{bitset_to_bin:N}.
% \end{function}
%
% \begin{function}[added = 2023-11-15]
%   {\bitset_show:N, \bitset_show:c}
%   \begin{syntax}
%     \cs{bitset_show:N} \meta{bitset var}
%   \end{syntax}
%   Displays the binary and decimal values of the \meta{bitset var}
%   on the terminal.
% \end{function}
%
% \begin{function}[added = 2023-11-15]
%   {\bitset_log:N, \bitset_log:c}
%   \begin{syntax}
%     \cs{bitset_log:N} \meta{bitset var}
%   \end{syntax}
%   Writes the binary and decimal values of the \meta{bitset var}
%   in the log file.
% \end{function}
%
% \begin{function}[added = 2023-11-15]
%  {
%     \bitset_show_named_index:N, \bitset_show_named_index:c
%  }
%   \begin{syntax}
%     \cs{bitset_show_named_index:N} \meta{bitset var}
%   \end{syntax}
%   Displays declared name--index pairs of the \meta{bitset var}
%   on the terminal.
% \end{function}
%
% \begin{function}[added = 2023-12-11]
%  {
%     \bitset_log_named_index:N, \bitset_log_named_index:c
%  }
%   \begin{syntax}
%     \cs{bitset_log_named_index:N} \meta{bitset var}
%   \end{syntax}
%   Writes declared name--index pairs of the \meta{bitset var}
%   in the log file.
% \end{function}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3bitset} implementation}
%
%    \begin{macrocode}
%<*code>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=bitset>
%    \end{macrocode}
%
% A bitset is a string variable.
%  \begin{macro}{\bitset_new:N, \bitset_new:c}
%  \begin{macro}{\bitset_new:Nn, \bitset_new:cn}
%    \begin{macrocode}
\cs_new_protected:Npn \bitset_new:N #1
  {
    \__kernel_chk_if_free_cs:N #1
    \cs_gset_eq:NN #1 \c_zero_str
    \prop_new:c { g__bitset_ \cs_to_str:N #1 _name_prop }
  }
\cs_new_protected:Npn \bitset_new:Nn #1 #2
  {
    \__kernel_chk_if_free_cs:N #1
    \cs_gset_eq:NN #1 \c_zero_str
    \prop_new:c { g__bitset_ \cs_to_str:N #1 _name_prop }
    \prop_gset_from_keyval:cn
      { g__bitset_ \cs_to_str:N #1 _name_prop }
      {#2}
  }
\cs_generate_variant:Nn \bitset_new:N { c }
\cs_generate_variant:Nn \bitset_new:Nn { c }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\bitset_addto_named_index:Nn}
%    \begin{macrocode}
\cs_new_protected:Npn \bitset_addto_named_index:Nn #1#2
  {
    \prop_gput_from_keyval:cn
      { g_@@_ \cs_to_str:N #1 _name_prop } { #2 }
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}[EXP,pTF]
%   {
%     \bitset_if_exist:N, \bitset_if_exist:c
%   }
% Existence tests.
%    \begin{macrocode}
\prg_new_eq_conditional:NNn
  \bitset_if_exist:N \str_if_exist:N { p , T , F , TF }
\prg_new_eq_conditional:NNn
  \bitset_if_exist:c \str_if_exist:c { p , T , F , TF }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_set_true:Nn, \@@_gset_true:Nn, \@@_set_false:Nn, \@@_gset_false:Nn}
% \begin{macro}{\@@_set:NNnN}
% The internal command uses only numbers (integer expressions) for the
% position.
% A bit is set by either extending the string or by splitting it and
% then inserting an $1$. It is not checked if the value was already $1$.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_set_true:Nn #1#2
  { \@@_set:NNnN \str_set:Ne #1 {#2} 1 }
\cs_new_protected:Npn \@@_gset_true:Nn #1#2
  { \@@_set:NNnN \str_gset:Ne #1 {#2} 1 }
\cs_new_protected:Npn \@@_set_false:Nn #1#2
  { \@@_set:NNnN \str_set:Ne #1 {#2} 0 }
\cs_new_protected:Npn \@@_gset_false:Nn #1#2
  { \@@_set:NNnN \str_gset:Ne #1 {#2} 0 }
\cs_new_protected:Npn \@@_set:NNnN #1#2#3#4
  {
    \int_compare:nNnT {#3} > { 0 }
      {
        \int_compare:nNnTF { \str_count:N #2 } < {#3}
          {
            #1 #2
              {
                #4
                \prg_replicate:nn { #3 - \str_count:N #2 - 1 } { 0 }
                #2
              }
          }
          {
            #1 #2
              {
                \str_range:Nnn #2 { 1 } { -1 - (#3) }
                #4
                \str_range:Nnn #2 { 1 - (#3) } { -1 }
              }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{variable}{\l_@@_tmp_int}
%    \begin{macrocode}
\int_new:N \l_@@_tmp_int
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[TF]{\@@_test_digits:n}
% \begin{macro}{\@@_test_digits_end:n}
% \begin{macro}{\@@_test_digits:w}
%   \url{https://chat.stackexchange.com/transcript/message/56878159#56878159}
%    \begin{macrocode}
\prg_new_protected_conditional:Npnn \@@_test_digits:n #1 { TF }
  {
    \tex_afterassignment:D \@@_test_digits:w
    \l_@@_tmp_int = 0 \tl_trim_spaces_apply:nN {#1} \tl_to_str:n
    \@@_test_digits_end:
    \use_i:nnn \if_false:
    \@@_test_digits_end:
    \if_int_compare:w \c_zero_int < \l_@@_tmp_int
      \prg_return_true:
    \else:
      \prg_return_false:
    \fi:
  }
\cs_new_eq:NN \@@_test_digits_end: \exp_stop_f:
\cs_new_protected:Npn \@@_test_digits:w #1 \@@_test_digits_end: { }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}
%   {
%     \bitset_set_true:Nn, \bitset_set_true:cn,
%     \bitset_gset_true:Nn, \bitset_gset_true:cn,
%     \bitset_set_false:Nn, \bitset_set_false:cn,
%     \bitset_gset_false:Nn, \bitset_gset_false:cn
%   }
% \begin{macro}{\@@_set_aux:NNn}
% The user commands must first translate the argument to an index number.
%    \begin{macrocode}
\cs_new_protected:Npn \bitset_set_true:Nn #1#2
  { \@@_set:NNn \@@_set_true:Nn #1 {#2} }
\cs_new_protected:Npn \bitset_gset_true:Nn #1#2
  { \@@_set:NNn \@@_gset_true:Nn #1 {#2} }
\cs_new_protected:Npn \bitset_set_false:Nn #1#2
  { \@@_set:NNn \@@_set_false:Nn #1 {#2} }
\cs_new_protected:Npn \bitset_gset_false:Nn #1#2
  { \@@_set:NNn \@@_gset_false:Nn #1 {#2} }
\cs_new_protected:Npn \@@_set:NNn #1#2#3
  {
    \prop_if_in:cnTF { g_@@_ \cs_to_str:N #2 _name_prop } {#3}
      {
        #1 #2
          {
            \prop_item:cn  { g_@@_ \cs_to_str:N #2 _name_prop } {#3}
          }
      }
      {
        \@@_test_digits:nTF {#3}
          {
            #1 #2 {#3}
            \prop_gput:cnn { g_@@_ \cs_to_str:N #2 _name_prop } {#3} {#3}
          }
          {
            \msg_warning:nnee { bitset } { unknown-name }
              { \token_to_str:N #2 }
              { \tl_to_str:n {#3} }
          }
      }
  }
\cs_generate_variant:Nn \bitset_set_true:Nn  { c }
\cs_generate_variant:Nn \bitset_gset_true:Nn { c }
\cs_generate_variant:Nn \bitset_set_false:Nn  { c }
\cs_generate_variant:Nn \bitset_gset_false:Nn { c }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}
%   {
%     \bitset_clear:N,  \bitset_clear:c,
%     \bitset_gclear:N, \bitset_gclear:c
%   }
%    \begin{macrocode}
\cs_new_protected:Npn \bitset_clear:N #1
  {
    \str_set_eq:NN #1 \c_zero_str
  }
\cs_new_protected:Npn \bitset_gclear:N #1
  {
    \str_gset_eq:NN #1 \c_zero_str
  }
\cs_generate_variant:Nn \bitset_clear:N { c }
\cs_generate_variant:Nn \bitset_gclear:N { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]
%   {
%     \bitset_to_arabic:N, \bitset_to_arabic:c,
%     \bitset_to_bin:N,    \bitset_to_bin:c,
%   }
% \begin{macro}[EXP]{\@@_to_int:nN}
%   The naming of the commands follow the names in the \texttt{int} module.
%   \cs{bitset_to_arabic:N} uses \cs{int_from_bin:n} if the string is shorter
%   than $32$ and the slower \cs{fp_eval:n} for larger bitsets.
%    \begin{macrocode}
\cs_new:Npn \bitset_to_arabic:N #1
  {
    \int_compare:nNnTF { \str_count:N #1 } < { 32 }
      { \exp_args:No \int_from_bin:n {#1} }
      {
        \exp_after:wN \@@_to_int:nN \exp_after:wN 0
        #1 \q_recursion_tail \q_recursion_stop
      }
  }
\cs_new:Npn \@@_to_int:nN #1#2
  {
    \quark_if_recursion_tail_stop_do:Nn #2 {#1}
    \exp_args:Nf \@@_to_int:nN { \fp_eval:n { #1 * 2 + #2 } }
  }
\cs_new:Npn \bitset_to_bin:N #1
  {
    #1
  }
\cs_generate_variant:Nn \bitset_to_arabic:N  { c }
\cs_generate_variant:Nn \bitset_to_bin:N { c }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\bitset_use:N, \bitset_use:c}
%    \begin{macrocode}
\cs_new_eq:NN \bitset_use:N \tl_use:N
\cs_generate_variant:Nn \bitset_use:N { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}
%   {
%     \bitset_item:Nn, \bitset_item:cn
%   }
% All bits that have been set at anytime have an entry in the prop,
% so we can take everything else as $0$.
%    \begin{macrocode}
\cs_new:Npn \bitset_item:Nn #1#2
  {
    \prop_if_in:cnTF { g_@@_ \cs_to_str:N #1 _name_prop } {#2}
      {
        \int_eval:n
          {
            \str_item:Nn #1
              { 0 - ( \prop_item:cn { g_@@_ \cs_to_str:N #1 _name_prop } {#2} ) }
            +0
          }
      }
      {
        0
      }
  }
\cs_generate_variant:Nn \bitset_item:Nn { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}
%   {
%     \bitset_show:N, \bitset_show:c,
%     \bitset_log:N,  \bitset_log:c
%   }
%    \begin{macrocode}
\cs_new_protected:Npn   \bitset_show:N { \@@_show:NN \msg_show:nneeee }
\cs_generate_variant:Nn \bitset_show:N { c }
\cs_new_protected:Npn   \bitset_log:N  { \@@_show:NN \msg_log:nneeee }
\cs_generate_variant:Nn \bitset_log:N  { c }
\cs_new_protected:Npn \@@_show:NN #1#2
  {
    \__kernel_chk_defined:NT #2
      {
        #1 { bitset } { show }
          { \token_to_str:N #2 }
          { \bitset_to_bin:N #2 }
          { \bitset_to_arabic:N #2 }
          { }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}
%  {
%     \bitset_show_named_index:N, \bitset_show_named_index:c,
%     \bitset_log_named_index:N,  \bitset_log_named_index:c
%  }
%    \begin{macrocode}
\cs_new_protected:Npn \bitset_show_named_index:N
  { \@@_show_named_index:NN \msg_show:nneeee }
\cs_generate_variant:Nn \bitset_show_named_index:N { c }
\cs_new_protected:Npn \bitset_log_named_index:N
  { \@@_show_named_index:NN \msg_log:nneeee }
\cs_generate_variant:Nn \bitset_log_named_index:N { c }
\cs_new_protected:Npn \@@_show_named_index:NN #1#2
  {
    \__kernel_chk_defined:NT #2
      {
        #1 { bitset } { show-names }
          { \token_to_str:N #2 }
          { \prop_map_function:cN { g_@@_ \cs_to_str:N #2 _name_prop } \msg_show_item:nn }
          { } { }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Messages}
%    \begin{macrocode}
\msg_new:nnn { bitset } { show }
  {
    The~bitset~#1~has~the~representation: \\
    >~binary:~#2  \\
    >~arabic:~#3 .
  }
\msg_new:nnn { bitset } { show-names }
  {
    The~bitset~#1~
    \tl_if_empty:nTF {#2}
      { knows~no~names~yet \\>~ . }
      { knows~the~name/index~pairs~(without~outer~braces): #2 . }
  }
\msg_new:nnn { bitset } { unknown-name }
  { The~name~'#2'~is~unknown~for~bitset~\tl_to_str:n {#1} }
\prop_gput:Nnn \g_msg_module_name_prop { bitset } { LaTeX }
\prop_gput:Nnn \g_msg_module_type_prop { bitset } { }
%    \end{macrocode}
%
%    \begin{macrocode}
%</code>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
