% \iffalse meta-comment % %% File: l3bitset.dtx % % Copyright (C) 2020-2024 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} % % \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 2024-03-14} % % \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}[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} %<*package> % \end{macrocode} % % \begin{macrocode} %<@@=bitset> % \end{macrocode} % % Transitional support. % \begin{macrocode} \cs_if_exist:NT \@expl@finalise@setup@@@@ { \tl_gput_right:Nn \@expl@finalise@setup@@@@ { \declare@file@substitution { l3bitset.sty } { null.tex } } } % \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_@@_internal_int} % \begin{macrocode} \int_new:N \l_@@_internal_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_@@_internal_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_@@_internal_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_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} % % \end{macrocode} % % \end{implementation} % % \PrintIndex