diff options
Diffstat (limited to 'macros/latex-dev/required/l3kernel/l3intarray.dtx')
-rw-r--r-- | macros/latex-dev/required/l3kernel/l3intarray.dtx | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/macros/latex-dev/required/l3kernel/l3intarray.dtx b/macros/latex-dev/required/l3kernel/l3intarray.dtx new file mode 100644 index 0000000000..bbc41c0cdb --- /dev/null +++ b/macros/latex-dev/required/l3kernel/l3intarray.dtx @@ -0,0 +1,974 @@ +% \iffalse meta-comment +% +%% File: l3intarray.dtx +% +% Copyright (C) 2017-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 +% +% 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} +%</driver> +% \fi +% +% +% \title{^^A +% The \pkg{l3intarray} module\\ 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 2024-04-11} +% +% \maketitle +% +% \begin{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. +% +% \section{Creating and initialising integer array variables} +% +% \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}[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{int expr 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} +% +% \section{Adding data to integer arrays} +% +% \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} +% +% \section{Couting entries in integer arrays} +% +% \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} +% +% \section{Using a single entry} +% +% \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} +% +% \section{Integer array conditional} +% +% \begin{function}[pTF, added = 2024-03-31] +% {\intarray_if_exist:N, \intarray_if_exist:c} +% \begin{syntax} +% \cs{intarray_if_exist_p:N} \meta{intarray~var} +% \cs{intarray_if_exist:NTF} \meta{intarray~var} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests whether the \meta{intarray~var} is currently defined. This +% does not check that the \meta{intarray~var} really is an integer +% array variable. +% \end{function} +% +% \section{Viewing integer arrays} +% +% \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} +% +% \section{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} module +% 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 +%</tex> +% \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') +%</lua> +% \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:Npe #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:nne { kernel } { negative-array-size } + { \intarray_count:N #1 } + } + } + \cs_generate_variant:Nn \intarray_new:Nn { c } +%</tex> +% \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') +%</lua> +% \end{macrocode} +% +% \begin{macrocode} +%<*tex> + \cs_generate_variant:Nn \intarray_count:N { c } +%</tex> +% \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') +%</lua> +% \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:nneee { 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 } +%</tex> +% \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') +%</lua> +%<*tex> + \cs_generate_variant:Nn \intarray_gzero:N { c } +%</tex> +% \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') +%</lua> +% \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]{\@@_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} +%</tex> +%<*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') +%</lua> +% \end{macrocode} +% \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} ~ + } +%</tex> +%<*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') +%</lua> +% \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: + } +%</tex> +%<*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') +%</lua> +% \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:nne { 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:nneee #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:nneeee { 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:Nw} +% 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]{\@@_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 \@@_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} +% +% \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}[pTF]{\intarray_if_exist:N, \intarray_if_exist:c} +% Copies of the \texttt{cs} functions defined in \pkg{l3basics}. +% \begin{macrocode} +\prg_new_eq_conditional:NNn \intarray_if_exist:N \cs_if_exist:N + { TF , T , F , p } +\prg_new_eq_conditional:NNn \intarray_if_exist:c \cs_if_exist:c + { TF , T , F , p } +% \end{macrocode} +% \end{macro} +% +% \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:nneeee } +\cs_generate_variant:Nn \intarray_show:N { c } +\cs_new_protected:Npn \intarray_log:N { \@@_show:NN \msg_log:nneeee } +\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} +% +% \begin{macrocode} +%</tex> +%</package> +% \end{macrocode} +% +% \end{implementation} +% +% \PrintIndex |