% \iffalse meta-comment % %% File: l3intarray.dtx % % Copyright (C) 2017-2022 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 % % https://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 \textsf{l3intarray} package: fast global integer arrays^^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 2022-01-12} % % \maketitle % % \begin{documentation} % % \section{\pkg{l3intarray} documentation} % % For applications requiring heavy use of integers, this module provides % arrays which can be accessed in constant time (contrast \pkg{l3seq}, % where access time is linear). These arrays have several important % features % \begin{itemize} % \item The size of the array is fixed and must be given at % point of initialisation % \item The absolute value of each entry has maximum $2^{30}-1$ % (\emph{i.e.}~one power lower than the usual \cs{c_max_int} % ceiling of $2^{31}-1$) % \end{itemize} % The use of \texttt{intarray} data is therefore recommended for cases where % the need for fast access is of paramount importance. % % \begin{function}[added = 2018-03-29]{\intarray_new:Nn, \intarray_new:cn} % \begin{syntax} % \cs{intarray_new:Nn} \meta{intarray~var} \Arg{size} % \end{syntax} % Evaluates the integer expression \meta{size} and allocates an % \meta{integer array variable} with that number of (zero) entries. % The variable name should start with |\g_| because assignments are % always global. % \end{function} % % \begin{function}[EXP, added = 2018-03-29]{\intarray_count:N, \intarray_count:c} % \begin{syntax} % \cs{intarray_count:N} \meta{intarray~var} % \end{syntax} % Expands to the number of entries in the \meta{integer array variable}. % Contrarily to \cs{seq_count:N} this is performed in constant time. % \end{function} % % \begin{function}[added = 2018-03-29]{\intarray_gset:Nnn, \intarray_gset:cnn} % \begin{syntax} % \cs{intarray_gset:Nnn} \meta{intarray~var} \Arg{position} \Arg{value} % \end{syntax} % Stores the result of evaluating the integer expression \meta{value} % into the \meta{integer array variable} at the (integer expression) % \meta{position}. If the \meta{position} is not between $1$ and the % \cs{intarray_count:N}, or the \meta{value}'s absolute value is % bigger than $2^{30}-1$, an error occurs. Assignments are always % global. % \end{function} % % \begin{function}[added = 2018-05-04] % {\intarray_const_from_clist:Nn, \intarray_const_from_clist:cn} % \begin{syntax} % \cs{intarray_const_from_clist:Nn} \meta{intarray~var} \meta{intexpr clist} % \end{syntax} % Creates a new constant \meta{integer array variable} or raises an % error if the name is already taken. The \meta{integer array % variable} is set (globally) to contain as its items the results of % evaluating each \meta{integer expression} in the \meta{comma list}. % \end{function} % % \begin{function}[added = 2018-05-04]{\intarray_gzero:N, \intarray_gzero:c} % \begin{syntax} % \cs{intarray_gzero:N} \meta{intarray~var} % \end{syntax} % Sets all entries of the \meta{integer array variable} to zero. % Assignments are always global. % \end{function} % % \begin{function}[EXP, added = 2018-03-29]{\intarray_item:Nn, \intarray_item:cn} % \begin{syntax} % \cs{intarray_item:Nn} \meta{intarray~var} \Arg{position} % \end{syntax} % Expands to the integer entry stored at the (integer expression) % \meta{position} in the \meta{integer array variable}. If the % \meta{position} is not between $1$ and the \cs{intarray_count:N}, an % error occurs. % \end{function} % % \begin{function}[EXP, added = 2018-05-05] % {\intarray_rand_item:N, \intarray_rand_item:c} % \begin{syntax} % \cs{intarray_rand_item:N} \meta{intarray~var} % \end{syntax} % Selects a pseudo-random item of the \meta{integer array}. If the % \meta{integer array} is empty, produce an error. % \end{function} % % \begin{function}[added = 2018-05-04] % {\intarray_show:N, \intarray_show:c, \intarray_log:N, \intarray_log:c} % \begin{syntax} % \cs{intarray_show:N} \meta{intarray~var} % \cs{intarray_log:N} \meta{intarray~var} % \end{syntax} % Displays the items in the \meta{integer array variable} in the % terminal or writes them in the log file. % \end{function} % % \subsection{Implementation notes} % % It is a wrapper around the \tn{fontdimen} primitive, used to store % arrays of integers (with a restricted range: absolute value at most % $2^{30}-1$). In contrast to \pkg{l3seq} sequences the access to % individual entries is done in constant time rather than linear time, % but only integers can be stored. More precisely, the primitive % \tn{fontdimen} stores dimensions but the \pkg{l3intarray} package % transparently converts these from/to integers. Assignments are always % global. % % While \LuaTeX{}'s memory is extensible, other engines can % \enquote{only} deal with a bit less than $4\times 10^6$ entries in all % \tn{fontdimen} arrays combined (with default \TeX{} Live settings). % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3intarray} implementation} % % \begin{macrocode} %<*package> % \end{macrocode} % % \begin{macrocode} %<@@=intarray> % \end{macrocode} % % There are two implementations for this module: One \cs{fontdimen} based one % for more traditional \TeX\ engines and a Lua based one for engines with Lua support. % % Both versions do not allow negative array sizes. % \begin{macrocode} %<*tex> \msg_new:nnn { kernel } { negative-array-size } { Size~of~array~may~not~be~negative:~#1 } % \end{macrocode} % % \begin{variable}{\l_@@_loop_int} % A loop index. % \begin{macrocode} \int_new:N \l_@@_loop_int % \end{macrocode} % \end{variable} % % \subsection{Lua implementation} % First, let's look at the Lua variant: % % We select the Lua version if the Lua helpers were defined. This can be detected by % the presence of \cs{@@_gset_count:Nw}. % % \begin{macrocode} \cs_if_exist:NTF \@@_gset_count:Nw { % \end{macrocode} % % \subsubsection{Allocating arrays} % % \begin{variable}{\g_@@_table_int, \l_@@_bad_index_int} % Used to differentiate intarrays in Lua and to record an invalid index. % \begin{macrocode} \int_new:N \g_@@_table_int \int_new:N \l_@@_bad_index_int % % \end{macrocode} % \end{variable} % % \begin{macro}{\@@:w} % Used as marker for intarrays in Lua. Followed by an unbraced number % identifying the array and a single space. This format is used to make it % easy to scan from Lua. % \begin{macrocode} %<*lua> luacmd('@@:w', function() scan_int() tex.error'LaTeX Error: Isolated intarray ignored' end, 'protected', 'global') % % \end{macrocode} % \end{macro} % % \begin{macro}{\intarray_new:Nn, \intarray_new:cn} % \begin{macro}{\@@_new:N} % Declare |#1| as a tokenlist with the scanmark and a unique number. % Pass the array's size to the Lua helper. % Every \texttt{intarray} must be global; it's enough to run this % check in \cs{intarray_new:Nn}. % \begin{macrocode} %<*tex> \cs_new_protected:Npn \@@_new:N #1 { \__kernel_chk_if_free_cs:N #1 \int_gincr:N \g_@@_table_int \cs_gset_nopar:Npx #1 { \@@:w \int_use:N \g_@@_table_int \c_space_tl } } \cs_new_protected:Npn \intarray_new:Nn #1#2 { \@@_new:N #1 \@@_gset_count:Nw #1 \int_eval:n {#2} \scan_stop: \int_compare:nNnT { \intarray_count:N #1 } < 0 { \msg_error:nnx { kernel } { negative-array-size } { \intarray_count:N #1 } } } \cs_generate_variant:Nn \intarray_new:Nn { c } % % \end{macrocode} % \end{macro} % \end{macro} % % Before we get to the first command implmented in Lua, we first need some % definitions. Since \texttt{token.create} only works correctly if \TeX{} % has seen the tokens before, we first run a short \TeX{} sequence to ensure % that all relevant control sequences are known. % \begin{macrocode} %<*lua> local scan_token = token.scan_token local put_next = token.put_next local intarray_marker = token_create_safe'@@:w' local use_none = token_create_safe'use_none:n' local use_i = token_create_safe'use:n' local expand_after_scan_stop = {token_create_safe'exp_after:wN', token_create_safe'scan_stop:'} local comma = token_create(string.byte',') % \end{macrocode} % % \begin{macro}{@@_table} % Internal helper to scan an intarray token, extract the associated % Lua table and return an error if the input is invalid. % % \begin{macrocode} local @@_table do local tables = get_luadata and get_luadata'@@' or {[0] = {}} function @@_table() local t = scan_token() if t ~= intarray_marker then put_next(t) tex.error'LaTeX Error: intarray expected' return tables[0] end local i = scan_int() local current_table = tables[i] if current_table then return current_table end current_table = {} tables[i] = current_table return current_table end % \end{macrocode} % Since in \LaTeX{} this is loaded in the format, we want to preserve any intarrays % which are created while format building for the actual run. % % To do this, we use the \texttt{register_luadata} mechanism from \pkg{l3luatex}: % Directly before the format get dumped, the following function gets invoked and serializes % all existing tables into a string. This string gets compiled and dumped into the format and % is made available at the beginning of regular runs as \texttt{get_luadata'@@'}. % \begin{macrocode} if register_luadata then register_luadata('@@', function() local t = "{[0]={}," for i=1, #tables do t = string.format("%s{%s},", t, table.concat(tables[i], ',')) end return t .. "}" end) end end % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\intarray_count:N, \intarray_count:c} % \begin{macro}[EXP]{\@@_gset_count:Nw} % Set and get the size of an array. ``Setting the size'' means in this context that % we add zeros until we reach the desired size. % \begin{macrocode} local sprint = tex.sprint luacmd('@@_gset_count:Nw', function() local t = @@_table() local n = scan_int() for i=#t+1, n do t[i] = 0 end end, 'protected', 'global') luacmd('intarray_count:N', function() sprint(-2, #@@_table()) end, 'global') % % \end{macrocode} % % \begin{macrocode} %<*tex> \cs_generate_variant:Nn \intarray_count:N { c } % % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Array items} % % \begin{macro}{\@@_gset:wF, \@@_gset:w} % The setter provided by Lua. The argument order somewhat emulates the |\fontdimen|: % First the array index, followed by the intarray and then the new value. % This has been chosen over a more conventional order to provide a delimiter for the numbers. % \begin{macrocode} %<*lua> luacmd('@@_gset:wF', function() local i = scan_int() local t = @@_table() if t[i] then t[i] = scan_int() put_next(use_none) else tex.count.l_@@_bad_index_int = i scan_int() put_next(use_i) end end, 'protected', 'global') luacmd('@@_gset:w', function() local i = scan_int() local t = @@_table() t[i] = scan_int() end, 'protected', 'global') % % \end{macrocode} % \end{macro} % % \begin{macro}{\intarray_gset:Nnn, \intarray_gset:cnn, \__kernel_intarray_gset:Nnn} % The \cs{__kernel_intarray_gset:Nnn} function does not use % \cs{int_eval:n}, namely its arguments must be suitable for % \cs{int_value:w}. The user version checks the position and value % are within bounds. % \begin{macrocode} %<*tex> \cs_new_protected:Npn \__kernel_intarray_gset:Nnn #1#2#3 { \@@_gset:w #2 #1 #3 \scan_stop: } \cs_new_protected:Npn \intarray_gset:Nnn #1#2#3 { \@@_gset:wF \int_eval:n {#2} #1 \int_eval:n{#3} { \msg_error:nnxxx { kernel } { out-of-bounds } { \token_to_str:N #1 } { \int_use:N \l_@@_bad_index_int } { \intarray_count:N #1 } } } \cs_generate_variant:Nn \intarray_gset:Nnn { c } % % \end{macrocode} % \end{macro} % % \begin{macro}{\intarray_gzero:N, \intarray_gzero:c} % Set the appropriate array entry to zero. No bound checking % needed. % \begin{macrocode} %<*lua> luacmd('intarray_gzero:N', function() local t = @@_table() for i=1, #t do t[i] = 0 end end, 'global', 'protected') % %<*tex> \cs_generate_variant:Nn \intarray_gzero:N { c } % % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\intarray_item:Nn, \intarray_item:cn, \__kernel_intarray_item:Nn} % \begin{macro}{\@@_item:wF,\@@_item:w} % Get the appropriate entry and perform bound checks. The % \cs{__kernel_intarray_item:Nn} function omits bound checks and omits % \cs{int_eval:n}, namely its argument must be a \TeX{} integer % suitable for \cs{int_value:w}. % \begin{macrocode} %<*lua> luacmd('@@_item:wF', function() local i = scan_int() local t = @@_table() local item = t[i] if item then put_next(use_none) else tex.l_@@_bad_index_int = i put_next(use_i) end put_next(expand_after_scan_stop) scan_token() if item then sprint(-2, item) end end, 'global') luacmd('@@_item:w', function() local i = scan_int() local t = @@_table() sprint(-2, t[i]) end, 'global') % % \end{macrocode} % % \begin{macrocode} %<*tex> \cs_new:Npn \__kernel_intarray_item:Nn #1#2 { \@@_item:w #2 #1 } \cs_new:Npn \intarray_item:Nn #1#2 { \@@_item:wF \int_eval:n {#2} #1 { \msg_expandable_error:nnfff { kernel } { out-of-bounds } { \token_to_str:N #1 } { \int_use:N \l_@@_bad_index_int } { \intarray_count:N #1 } 0 } } \cs_generate_variant:Nn \intarray_item:Nn { c } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\intarray_rand_item:N, \intarray_rand_item:c} % Importantly, \cs{intarray_item:Nn} only evaluates its argument once. % \begin{macrocode} \cs_new:Npn \intarray_rand_item:N #1 { \intarray_item:Nn #1 { \int_rand:n { \intarray_count:N #1 } } } \cs_generate_variant:Nn \intarray_rand_item:N { c } % \end{macrocode} % \end{macro} % % \subsubsection{Working with contents of integer arrays} % % \begin{macro}{\intarray_const_from_clist:Nn, \intarray_const_from_clist:cn} % We use the \cs{__kernel_intarray_gset:Nnn} which does not do bounds checking % and instead automatically resizes the array. % This is not implemented in Lua to ensure that the clist parsing is consistent % with the clist module. % \begin{macrocode} \cs_new_protected:Npn \intarray_const_from_clist:Nn #1#2 { \@@_new:N #1 \int_zero:N \l_@@_loop_int \clist_map_inline:nn {#2} { \int_incr:N \l_@@_loop_int \__kernel_intarray_gset:Nnn #1 \l_@@_loop_int { \int_eval:n {##1} } } } \cs_generate_variant:Nn \intarray_const_from_clist:Nn { c } % \end{macrocode} % \end{macro} % % \begin{macro}[rEXP]{\intarray_to_clist:N, \intarray_to_clist:c} % \begin{macro}[rEXP]{\@@_to_clist:Nn, \@@_to_clist:w} % The \cs{@@_to_clist:Nn} auxiliary allows to choose the delimiter and % is also used in \cs{intarray_show:N}. Here we just pass the information % to Lua and let \texttt{table.concat} do the actual work. % We discard the category codes of the passed delimiter but this is not % an issue since the delimiter is always just a comma or a comma and a space. % In both cases \texttt{sprint(2, ...)} provides the right catcodes. % \begin{macrocode} \cs_new:Npn \intarray_to_clist:N #1 { \@@_to_clist:Nn #1 { , } } \cs_generate_variant:Nn \intarray_to_clist:N { c } % %<*lua> local concat = table.concat luacmd('@@_to_clist:Nn', function() local t = @@_table() local sep = token.scan_string() sprint(-2, concat(t, sep)) end, 'global') % % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[rEXP]{\__kernel_intarray_range_to_clist:Nnn, \@@_range_to_clist:w} % Loop through part of the array. % \begin{macrocode} %<*tex> \cs_new:Npn \__kernel_intarray_range_to_clist:Nnn #1#2#3 { \@@_range_to_clist:w #1 \int_eval:n {#2} ~ \int_eval:n {#3} ~ } % %<*lua> luacmd('@@_range_to_clist:w', function() local t = @@_table() local from = scan_int() local to = scan_int() sprint(-2, concat(t, ',', from, to)) end, 'global') % % \end{macrocode} % \end{macro} % % \begin{macro}{\__kernel_intarray_gset_range_from_clist:Nnn, \@@_gset_range:nNw} % Loop through part of the array. We allow additional commas at the end. % \begin{macrocode} %<*tex> \cs_new_protected:Npn \__kernel_intarray_gset_range_from_clist:Nnn #1#2#3 { \@@_gset_range:w \int_eval:w #2 #1 #3 , , \scan_stop: } % %<*lua> luacmd('@@_gset_range:w', function() local from = scan_int() local t = @@_table() while true do local tok = scan_token() if tok == comma then repeat tok = scan_token() until tok ~= comma break else put_next(tok) end t[from] = scan_int() scan_token() from = from + 1 end end, 'global', 'protected') % % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_gset_overflow_test:nw} % In order to allow some code sharing later we provide the % \cs{@@_gset_overflow_test:nw} name here. It doesn't actually test anything % since the Lua implementation accepts all integers which could be tested with % \cs{tex_ifabsnum:D}. % \begin{macrocode} %<*tex> \cs_new_protected:Npn \@@_gset_overflow_test:nw #1 { } % \end{macrocode} % \end{macro} % % \subsection{Font dimension based implementation} % % Go to the false branch of the conditional above. % \begin{macrocode} } { % \end{macrocode} % % \subsubsection{Allocating arrays} % % \begin{macro}{\@@_entry:w, \@@_count:w} % We use these primitives quite a lot in this module. % \begin{macrocode} \cs_new_eq:NN \@@_entry:w \tex_fontdimen:D \cs_new_eq:NN \@@_count:w \tex_hyphenchar:D % \end{macrocode} % \end{macro} % % \begin{variable}{\c_@@_sp_dim} % Used to convert integers to dimensions fast. % \begin{macrocode} \dim_const:Nn \c_@@_sp_dim { 1 sp } % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_font_int} % Used to assign one font per array. % \begin{macrocode} \int_new:N \g_@@_font_int % \end{macrocode} % \end{variable} % % \begin{macro}{\intarray_new:Nn, \intarray_new:cn} % \begin{macro}{\@@_new:N} % Declare |#1| to be a font (arbitrarily |cmr10| at a never-used % size). Store the array's size as the \tn{hyphenchar} of that font % and make sure enough \tn{fontdimen} are allocated, by setting the % last one. Then clear any \tn{fontdimen} that |cmr10| starts with. % It seems \LuaTeX{}'s |cmr10| has an extra \tn{fontdimen} parameter % number $8$ compared to other engines (for a math font we would % replace $8$ by $22$ or some such). % Every \texttt{intarray} must be global; it's enough to run this % check in \cs{intarray_new:Nn}. % \begin{macrocode} \cs_new_protected:Npn \@@_new:N #1 { \__kernel_chk_if_free_cs:N #1 \int_gincr:N \g_@@_font_int \tex_global:D \tex_font:D #1 = cmr10~at~ \g_@@_font_int \c_@@_sp_dim \scan_stop: \int_step_inline:nn { 8 } { \__kernel_intarray_gset:Nnn #1 {##1} \c_zero_int } } \cs_new_protected:Npn \intarray_new:Nn #1#2 { \@@_new:N #1 \@@_count:w #1 = \int_eval:n {#2} \scan_stop: \int_compare:nNnT { \intarray_count:N #1 } < 0 { \msg_error:nnx { kernel } { negative-array-size } { \intarray_count:N #1 } } \int_compare:nNnT { \intarray_count:N #1 } > 0 { \__kernel_intarray_gset:Nnn #1 { \intarray_count:N #1 } { 0 } } } \cs_generate_variant:Nn \intarray_new:Nn { c } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\intarray_count:N, \intarray_count:c} % Size of an array. % \begin{macrocode} \cs_new:Npn \intarray_count:N #1 { \int_value:w \@@_count:w #1 } \cs_generate_variant:Nn \intarray_count:N { c } % \end{macrocode} % \end{macro} % % \subsubsection{Array items} % % \begin{macro}[EXP]{\@@_signed_max_dim:n} % Used when an item to be stored is larger than \cs{c_max_dim} in % absolute value; it is replaced by $\pm\cs{c_max_dim}$. % \begin{macrocode} \cs_new:Npn \@@_signed_max_dim:n #1 { \int_value:w \int_compare:nNnT {#1} < 0 { - } \c_max_dim } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_bounds:NNnTF, \@@_bounds_error:NNnw} % The functions \cs{intarray_gset:Nnn} and \cs{intarray_item:Nn} share % bounds checking. The |T| branch is used if |#3| is within bounds of % the array |#2|. % \begin{macrocode} \cs_new:Npn \@@_bounds:NNnTF #1#2#3 { \if_int_compare:w 1 > #3 \exp_stop_f: \@@_bounds_error:NNnw #1 #2 {#3} \else: \if_int_compare:w #3 > \intarray_count:N #2 \exp_stop_f: \@@_bounds_error:NNnw #1 #2 {#3} \fi: \fi: \use_i:nn } \cs_new:Npn \@@_bounds_error:NNnw #1#2#3#4 \use_i:nn #5#6 { #4 #1 { kernel } { out-of-bounds } { \token_to_str:N #2 } {#3} { \intarray_count:N #2 } #6 } % \end{macrocode} % \end{macro} % % \begin{macro}{\intarray_gset:Nnn, \intarray_gset:cnn, \__kernel_intarray_gset:Nnn} % \begin{macro}{\@@_gset:Nnn, \@@_gset_overflow:Nnn} % Set the appropriate \tn{fontdimen}. The % \cs{__kernel_intarray_gset:Nnn} function does not use % \cs{int_eval:n}, namely its arguments must be suitable for % \cs{int_value:w}. The user version checks the position and value % are within bounds. % \begin{macrocode} \cs_new_protected:Npn \__kernel_intarray_gset:Nnn #1#2#3 { \@@_entry:w #2 #1 #3 \c_@@_sp_dim } \cs_new_protected:Npn \intarray_gset:Nnn #1#2#3 { \exp_after:wN \@@_gset:Nww \exp_after:wN #1 \int_value:w \int_eval:n {#2} \exp_after:wN ; \int_value:w \int_eval:n {#3} ; } \cs_generate_variant:Nn \intarray_gset:Nnn { c } \cs_new_protected:Npn \@@_gset:Nww #1#2 ; #3 ; { \@@_bounds:NNnTF \msg_error:nnxxx #1 {#2} { \@@_gset_overflow_test:nw {#3} \__kernel_intarray_gset:Nnn #1 {#2} {#3} } { } } \cs_if_exist:NTF \tex_ifabsnum:D { \cs_new_protected:Npn \@@_gset_overflow_test:nw #1 { \tex_ifabsnum:D #1 > \c_max_dim \exp_after:wN \@@_gset_overflow:NNnn \fi: } } { \cs_new_protected:Npn \@@_gset_overflow_test:nw #1 { \if_int_compare:w \int_abs:n {#1} > \c_max_dim \exp_after:wN \@@_gset_overflow:NNnn \fi: } } \cs_new_protected:Npn \@@_gset_overflow:NNnn #1#2#3#4 { \msg_error:nnxxxx { kernel } { overflow } { \token_to_str:N #2 } {#3} {#4} { \@@_signed_max_dim:n {#4} } #1 #2 {#3} { \@@_signed_max_dim:n {#4} } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\intarray_gzero:N, \intarray_gzero:c} % Set the appropriate \tn{fontdimen} to zero. No bound checking % needed. The \cs{prg_replicate:nn} possibly uses quite a lot of % memory, but this is somewhat comparable to the size of the array, % and it is much faster than an \cs{int_step_inline:nn} loop. % \begin{macrocode} \cs_new_protected:Npn \intarray_gzero:N #1 { \int_zero:N \l_@@_loop_int \prg_replicate:nn { \intarray_count:N #1 } { \int_incr:N \l_@@_loop_int \@@_entry:w \l_@@_loop_int #1 \c_zero_dim } } \cs_generate_variant:Nn \intarray_gzero:N { c } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\intarray_item:Nn, \intarray_item:cn, \__kernel_intarray_item:Nn} % \begin{macro}{\@@_item:Nn} % Get the appropriate \tn{fontdimen} and perform bound checks. The % \cs{__kernel_intarray_item:Nn} function omits bound checks and omits % \cs{int_eval:n}, namely its argument must be a \TeX{} integer % suitable for \cs{int_value:w}. % \begin{macrocode} \cs_new:Npn \__kernel_intarray_item:Nn #1#2 { \int_value:w \@@_entry:w #2 #1 } \cs_new:Npn \intarray_item:Nn #1#2 { \exp_after:wN \@@_item:Nw \exp_after:wN #1 \int_value:w \int_eval:n {#2} ; } \cs_generate_variant:Nn \intarray_item:Nn { c } \cs_new:Npn \@@_item:Nw #1#2 ; { \@@_bounds:NNnTF \msg_expandable_error:nnfff #1 {#2} { \__kernel_intarray_item:Nn #1 {#2} } { 0 } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\intarray_rand_item:N, \intarray_rand_item:c} % Importantly, \cs{intarray_item:Nn} only evaluates its argument once. % \begin{macrocode} \cs_new:Npn \intarray_rand_item:N #1 { \intarray_item:Nn #1 { \int_rand:n { \intarray_count:N #1 } } } \cs_generate_variant:Nn \intarray_rand_item:N { c } % \end{macrocode} % \end{macro} % % \subsubsection{Working with contents of integer arrays} % % \begin{macro}{\intarray_const_from_clist:Nn, \intarray_const_from_clist:cn} % \begin{macro}{\@@_const_from_clist:nN} % Similar to \cs{intarray_new:Nn} (which we don't use because when % debugging is enabled that function checks the variable name starts % with |g_|). We make use of the fact that \TeX{} allows allocation % of successive \tn{fontdimen} as long as no other font has been % declared: no need to count the comma list items first. We need the % code in \cs{intarray_gset:Nnn} that checks the item value is not too % big, namely \cs{@@_gset_overflow_test:nw}, but not the code that % checks bounds. At the end, set the size of the intarray. % \begin{macrocode} \cs_new_protected:Npn \intarray_const_from_clist:Nn #1#2 { \@@_new:N #1 \int_zero:N \l_@@_loop_int \clist_map_inline:nn {#2} { \exp_args:Nf \@@_const_from_clist:nN { \int_eval:n {##1} } #1 } \@@_count:w #1 \l_@@_loop_int } \cs_generate_variant:Nn \intarray_const_from_clist:Nn { c } \cs_new_protected:Npn \@@_const_from_clist:nN #1#2 { \int_incr:N \l_@@_loop_int \@@_gset_overflow_test:nw {#1} \__kernel_intarray_gset:Nnn #2 \l_@@_loop_int {#1} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[rEXP]{\intarray_to_clist:N, \intarray_to_clist:c} % \begin{macro}[rEXP]{\@@_to_clist:Nn, \@@_to_clist:w} % Loop through the array, putting a comma before each item. Remove % the leading comma with |f|-expansion. We also use the auxiliary in % \cs{intarray_show:N} with argument comma, space. % \begin{macrocode} \cs_new:Npn \intarray_to_clist:N #1 { \@@_to_clist:Nn #1 { , } } \cs_generate_variant:Nn \intarray_to_clist:N { c } \cs_new:Npn \@@_to_clist:Nn #1#2 { \int_compare:nNnF { \intarray_count:N #1 } = \c_zero_int { \exp_last_unbraced:Nf \use_none:n { \@@_to_clist:w 1 ; #1 {#2} \prg_break_point: } } } \cs_new:Npn \@@_to_clist:w #1 ; #2#3 { \if_int_compare:w #1 > \@@_count:w #2 \prg_break:n \fi: #3 \__kernel_intarray_item:Nn #2 {#1} \exp_after:wN \@@_to_clist:w \int_value:w \int_eval:w #1 + \c_one_int ; #2 {#3} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[rEXP]{\__kernel_intarray_range_to_clist:Nnn, \@@_range_to_clist:ww} % Loop through part of the array. % \begin{macrocode} \cs_new:Npn \__kernel_intarray_range_to_clist:Nnn #1#2#3 { \exp_last_unbraced:Nf \use_none:n { \exp_after:wN \@@_range_to_clist:ww \int_value:w \int_eval:w #2 \exp_after:wN ; \int_value:w \int_eval:w #3 ; #1 \prg_break_point: } } \cs_new:Npn \@@_range_to_clist:ww #1 ; #2 ; #3 { \if_int_compare:w #1 > #2 \exp_stop_f: \prg_break:n \fi: , \__kernel_intarray_item:Nn #3 {#1} \exp_after:wN \@@_range_to_clist:ww \int_value:w \int_eval:w #1 + \c_one_int ; #2 ; #3 } % \end{macrocode} % \end{macro} % % \begin{macro}{\__kernel_intarray_gset_range_from_clist:Nnn, \@@_gset_range:Nw} % Loop through part of the array. % \begin{macrocode} \cs_new_protected:Npn \__kernel_intarray_gset_range_from_clist:Nnn #1#2#3 { \int_set:Nn \l_@@_loop_int {#2} \@@_gset_range:Nw #1 #3 , , \prg_break_point: } \cs_new_protected:Npn \@@_gset_range:Nw #1 #2 , { \if_catcode:w \scan_stop: \tl_to_str:n {#2} \scan_stop: \prg_break:n \fi: \__kernel_intarray_gset:Nnn #1 \l_@@_loop_int {#2} \int_incr:N \l_@@_loop_int \@@_gset_range:Nw #1 } % \end{macrocode} % \end{macro} % % \begin{macrocode} } % \end{macrocode} % % \subsection{Common parts} % % \begin{macro}{\intarray_show:N, \intarray_show:c, \intarray_log:N, \intarray_log:c} % Convert the list to a comma list (with spaces after each comma) % \begin{macrocode} \cs_new_protected:Npn \intarray_show:N { \@@_show:NN \msg_show:nnxxxx } \cs_generate_variant:Nn \intarray_show:N { c } \cs_new_protected:Npn \intarray_log:N { \@@_show:NN \msg_log:nnxxxx } \cs_generate_variant:Nn \intarray_log:N { c } \cs_new_protected:Npn \@@_show:NN #1#2 { \__kernel_chk_defined:NT #2 { #1 { intarray } { show } { \token_to_str:N #2 } { \intarray_count:N #2 } { >~ \@@_to_clist:Nn #2 { , ~ } } { } } } % \end{macrocode} % \end{macro} % % \subsubsection{Random arrays} % % \begin{macro}{\intarray_gset_rand:Nn, \intarray_gset_rand:cn} % \begin{macro}{\intarray_gset_rand:Nnn, \intarray_gset_rand:cnn} % \begin{macro} % { % \@@_gset_rand:Nnn, % \@@_gset_rand:Nff, % \@@_gset_rand_auxi:Nnnn, % \@@_gset_rand_auxii:Nnnn, % \@@_gset_rand_auxiii:Nnnn, % \@@_gset_all_same:Nn, % } % We only perform the bounds checks once. This is done by two % \cs{@@_gset_overflow_test:nw}, with an appropriate empty argument to % avoid a spurious \enquote{at position \texttt{\#1}} part in the % error message. Then calculate the number of choices: this is at % most $(2^{30}-1)-(-(2^{30}-1))+1=2^{31}-1$, which just barely does % not overflow. For small ranges use \cs{__kernel_randint:n} (making % sure to subtract~$1$ \emph{before} adding the random number to the % \meta{min}, to avoid overflow when \meta{min} or \meta{max} are % $\pm\cs{c_max_int}$), otherwise \cs{__kernel_randint:nn}. Finally, % if there are no random numbers do not define any of the auxiliaries. % \begin{macrocode} \cs_new_protected:Npn \intarray_gset_rand:Nn #1 { \intarray_gset_rand:Nnn #1 { 1 } } \cs_generate_variant:Nn \intarray_gset_rand:Nn { c } \sys_if_rand_exist:TF { \cs_new_protected:Npn \intarray_gset_rand:Nnn #1#2#3 { \@@_gset_rand:Nff #1 { \int_eval:n {#2} } { \int_eval:n {#3} } } \cs_new_protected:Npn \@@_gset_rand:Nnn #1#2#3 { \int_compare:nNnTF {#2} > {#3} { \msg_expandable_error:nnnn { kernel } { randint-backward-range } {#2} {#3} \@@_gset_rand:Nnn #1 {#3} {#2} } { \@@_gset_overflow_test:nw {#2} \@@_gset_rand_auxi:Nnnn #1 { } {#2} {#3} } } \cs_generate_variant:Nn \@@_gset_rand:Nnn { Nff } \cs_new_protected:Npn \@@_gset_rand_auxi:Nnnn #1#2#3#4 { \@@_gset_overflow_test:nw {#4} \@@_gset_rand_auxii:Nnnn #1 { } {#4} {#3} } \cs_new_protected:Npn \@@_gset_rand_auxii:Nnnn #1#2#3#4 { \exp_args:NNf \@@_gset_rand_auxiii:Nnnn #1 { \int_eval:n { #3 - #4 + 1 } } {#4} {#3} } \cs_new_protected:Npn \@@_gset_rand_auxiii:Nnnn #1#2#3#4 { \exp_args:NNf \@@_gset_all_same:Nn #1 { \int_compare:nNnTF {#2} > \c__kernel_randint_max_int { \exp_stop_f: \int_eval:n { \__kernel_randint:nn {#3} {#4} } } { \exp_stop_f: \int_eval:n { \__kernel_randint:n {#2} - 1 + #3 } } } } \cs_new_protected:Npn \@@_gset_all_same:Nn #1#2 { \int_zero:N \l_@@_loop_int \prg_replicate:nn { \intarray_count:N #1 } { \int_incr:N \l_@@_loop_int \__kernel_intarray_gset:Nnn #1 \l_@@_loop_int {#2} } } } { \cs_new_protected:Npn \intarray_gset_rand:Nnn #1#2#3 { \msg_error:nnn { kernel } { fp-no-random } { \intarray_gset_rand:Nnn #1 {#2} {#3} } } } \cs_generate_variant:Nn \intarray_gset_rand:Nnn { c } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macrocode} % % % \end{macrocode} % % \end{implementation} % % \PrintIndex