% \iffalse % vim: set expandtab: % vim: set shiftwidth=2: % vim: set tabstop=2: % \fi % \iffalse meta-comment % % Copyright (C) 2026 by Lukas Heindl % --------------------------------------------------------------------------- % This work may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3c % of this license or (at your option) any later version. % The latest version of this license is in % http://www.latex-project.org/lppl.txt % and version 1.3c or later is part of all distributions of LaTeX % version 2008/05/04 or later. % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is Lukas Heindl. % % This work consists of all files listed in manifest.txt. % % \fi % % \iffalse %<*driver> \ProvidesFile{hexdumptikz-selector-parser.dtx} % %\NeedsTeXFormat{LaTeX2e}[2022-06-01] % %<*driver> \begin{document} \DocInput{\jobname.dtx} \PrintChanges \PrintIndex \end{document} % % \changes{v0.0.0}{2026-05-14}{First draft} % % % \fi % % \iffalse %<*package> %<@@=hexdumptikz_selector_parser> % \fi % % \maketitle % % \begin{abstract} % Parser for the selection logic. % Parses an (user supplied) \enquote{string} to a \emph{context} which can be queried later. % \end{abstract} % % Identify the package and give the over all version information. % \begin{macrocode} \ProvidesExplPackage {hexdumptikz-selector-parser} {2026-06-20} {1.0.1} {Parse address/hexdump selectors} % \end{macrocode} % % Load internal dependencies % \begin{macrocode} \RequirePackage { hexdumptikz-addrcalc } % \end{macrocode} % % Create a shortcut for obtaining the variable name of a variable in a context. % Only for being able to use \emph{@@} in the \emph{.dtx} file. % \begin{macrocode} \cs_new_eq:NN \@@_ctx_var:nn \__hexdumptikz_selector_ctx_var:nn % \end{macrocode} % % \subsection{Variables} % Generally, the parsers parse the input to (local) variables. % Then later, the information from these variables gets collectively pushed to the context. % % Predicate related variables % \begin{var}{\l_@@_pred_kind_tl} % Type of the predicate % \begin{macrocode} \tl_new:N \l_@@_pred_kind_tl % \end{macrocode} % \end{var} % \begin{var}{\l_@@_pred_axis_tl} % On which axis the predicate operates on (\emph{x} or \emph{y})? % \begin{macrocode} \tl_new:N \l_@@_pred_axis_tl % \end{macrocode} % \end{var} % \begin{var}{\l_@@_pred_mod_int} % For modulo predicates: what is the modulo ($x/y \bmod \mathbf{m} = r$) of this rule % \begin{macrocode} \int_new:N \l_@@_pred_mod_int % \end{macrocode} % \end{var} % \begin{var}{\l_@@_pred_res_int} % For modulo predicates: what is the residue ($x/y \bmod m = \mathbf{r}$) of this rule % \begin{macrocode} \int_new:N \l_@@_pred_res_int % \end{macrocode} % \end{var} % % \begin{var}{\l_@@_kind_tl} % Type/Kind of the current rule % \begin{macrocode} \tl_new:N \l_@@_kind_tl % \end{macrocode} % \end{var} % % \begin{var}{\l_@@_low_tl} % \begin{var}{\l_@@_high_tl} % Tokenlist-low/high part of the current rule (typically the address) % \begin{macrocode} \tl_new:N \l_@@_low_tl \tl_new:N \l_@@_high_tl % \end{macrocode} % \end{var} % \end{var} % % \begin{var}{\l_@@_low_x_int} % \begin{var}{\l_@@_low_y_int} % \begin{var}{\l_@@_high_x_int} % \begin{var}{\l_@@_high_y_int} % Integer low/high x/y part of the current rule (typically the index) % \begin{macrocode} \int_new:N \l_@@_low_x_int \int_new:N \l_@@_low_y_int \int_new:N \l_@@_high_x_int \int_new:N \l_@@_high_y_int % \end{macrocode} % \end{var} % \end{var} % \end{var} % \end{var} % % \begin{var}{\l_@@_gap_count_int} % Amount of gap-nodes to insert before the current rule takes effect % \begin{macrocode} \int_new:N \l_@@_gap_count_int % \end{macrocode} % \end{var} % % \begin{var}{\l_@@_style_tl} % Style which to apply when this rule matches % \begin{macrocode} \tl_new:N \l_@@_style_tl % \end{macrocode} % \end{var} % % \begin{var}{\l_@@_pred_idx_int} % Which predicate to check when this rule is in range (index in the context's predicates sequence) % \begin{macrocode} \int_new:N \l_@@_pred_idx_int % \end{macrocode} % \end{var} % % \begin{var}{\l_@@_tmpa_tl} % Scratch-like variable: % \begin{macrocode} \tl_new:N \l_@@_tmpa_tl % \end{macrocode} % \end{var} % % \subsection{Regexes} % \begin{var}{\c_@@_mod_regex} % Rexex for parsing modulo predicates. % Also extracts the arguments with capture groups. % \begin{macrocode} \regex_const:Nn \c_@@_mod_regex { \A mod \( \s* ([0-9]+) \s* , \s* ([0-9]+) \s* \) \Z } % \end{macrocode} % \end{var} % % \subsection{Parsers for individual components} % \begin{fn}{\@@_parse_pred:n} % Parse a predicate. % Yes it is not nice to force parsing into (local) variables, but that many argument would also be not so nice % \begin{sideeffects} % \sclobber & \sdir & \texttt{l\_tmpa\_tl} \\ % \sclobber & \sdir & \texttt{l\_tmpa\_seq} \\ % \end{sideeffects} % \begin{args} % 1 & \ain & tl variable to parse \\ % - & \aout & \texttt{l\_@@\_pred\_axis\_tl} \\ % - & \aout & \texttt{l\_@@\_pred\_kind\_tl} \\ % - & \aout & \texttt{l\_@@\_pred\_mod\_tl} \\ % - & \aout & \texttt{l\_@@\_pred\_res\_tl} \\ % - & \ain & \texttt{l\_hexdumptikz\_common\_bytes\_per\_row\_int} \\ % \end{args} % \begin{macrocode} \cs_new_protected:Npn \@@_parse_pred:n #1 { \tl_set:Ne \l_tmpa_tl { \tl_trim_spaces:n {#1} } \tl_if_empty:NF \l_tmpa_tl { \seq_set_split:Nne \l_tmpa_seq { / } { \l_tmpa_tl } \int_case:nnF { \seq_count:N \l_tmpa_seq } { { 1 } { % \end{macrocode} % No axis prefix $\to$ default axis = x % \begin{macrocode} \tl_set:Nn \l_@@_pred_axis_tl { x } \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 1 } } } { 2 } { \tl_set:Ne \l_@@_pred_axis_tl { \seq_item:Nn \l_tmpa_seq { 1 } } \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 2 } } \str_case:VnF \l_@@_pred_axis_tl { { x } { } { y } { } } { \msg_critical:nnn { hexdumptikz-selector } { invalid-predicate-axis } { #1 } } } } { \msg_critical:nnn { hexdumptikz-selector } { invalid-predicate-input } { #1 } } % \end{macrocode} % % Parse the right-hand-side % \begin{macrocode} \str_case:VnF \l_tmpa_tl { { odd } { \tl_set:Nn \l_@@_pred_kind_tl { mod } \int_set:Nn \l_@@_pred_mod_int { 2 } \int_set:Nn \l_@@_pred_res_int { 1 } } { even } { \tl_set:Nn \l_@@_pred_kind_tl { mod } \int_set:Nn \l_@@_pred_mod_int { 2 } \int_set:Nn \l_@@_pred_res_int { 0 } } } { % \end{macrocode} % % Else assume it is plain \emph{mod(s,i )} % \begin{macrocode} \regex_extract_once:NVNTF \c_@@_mod_regex \l_tmpa_tl \l_tmpa_seq { \tl_set:Nn \l_@@_pred_kind_tl { mod } \int_set:Nn \l_@@_pred_mod_int { \seq_item:Nn \l_tmpa_seq { 2 } } % noqa: S103 \int_set:Nn \l_@@_pred_res_int { \seq_item:Nn \l_tmpa_seq { 3 } } % noqa: S103 % \end{macrocode} % % Enforce additional constraints when the predicate is about the x-axis % \begin{macrocode} \tl_if_eq:NnT \l_@@_pred_axis_tl { x } { \int_compare:nNnF { \int_mod:nn { \l_hexdumptikz_common_bytes_per_row_int } { \l_@@_pred_mod_int } } = { 0 } { \msg_critical:nnVV { hexdumptikz-selector } { invalid-mod } \l_hexdumptikz_common_bytes_per_row_int \l_@@_pred_mod_int } } % \end{macrocode} % % Sanity check on the modulos arguments % \begin{macrocode} \int_compare:nNnF { \l_@@_pred_res_int } < { \l_@@_pred_mod_int } { \msg_warning:nnVV { hexdumptikz-selector } { weird-mod } \l_@@_pred_mod_int \l_@@_pred_res_int } } { \msg_critical:nnn { hexdumptikz-selector } { unknown-predicate } { #1 } } } } } \cs_generate_variant:Nn \@@_parse_pred:n { V } % \end{macrocode} % \end{fn} % % \begin{fn}{\@@_parse_coord:NNN} % Parse a coordinate (y and optionally also x). % \begin{sideeffects} % \sclobber & \sdir & \texttt{l\_tmpa\_seq} \\ % \end{sideeffects} % \begin{args} % 1 & \ain & tl variable to parse \\ % 2 & \ain & parsed \emph{x}-coordinate (zero if unset) \\ % 3 & \ain & parsed \emph{y}-coordinate \\ % \end{args} % \begin{macrocode} \cs_new_protected:Npn \@@_parse_coord:NNN #1 #2 #3 { \seq_set_split:NnV \l_tmpa_seq { / } #1 \int_case:nnF { \seq_count:N \l_tmpa_seq } { { 1 } { \int_zero:N #2 \int_set:Nn #3 { \seq_item:Nn \l_tmpa_seq { 1 } } } { 2 } { \int_set:Nn #2 { \seq_item:Nn \l_tmpa_seq { 2 } } \int_set:Nn #3 { \seq_item:Nn \l_tmpa_seq { 1 } } } } { \msg_critical:nnN { hexdumptikz-selector } { invalid-coordinate } #1 } } % \end{macrocode} % \end{fn} % % \begin{fn}{\@@_parse_addr:NNN} % Parse an address. % \begin{sideeffects} % \sclobber & \sdir & \texttt{l\_tmpa\_seq} \\ % \sclobber & \sdir & \texttt{l\_@@\_tmpa\_int} \\ % \end{sideeffects} % \begin{args} % 1 & \ain & tl variable to parse \\ % 2 & \ain & parsed \emph{x}-coordinate (int) \\ % 3 & \ain & parsed address (tl) \\ % \end{args} % \begin{macrocode} \cs_new_protected:Npn \@@_parse_addr:NNN #1 #2 #3 { \hexdumptikz_address_to_nodename_components:eNN { #1 } #3 \l_@@_tmpa_tl \tl_put_left:Nn #3 { 0x } \int_set:Nn #2 { \l_@@_tmpa_tl } } % \end{macrocode} % \end{fn} % % \begin{fn}{\@@_parse_core:n} % Parses the whole range (without style or predicate) % \begin{sideeffects} % \sclobber & \sdir & \texttt{l\_tmpa\_seq} \\ % \sclobber & \sdir & \texttt{l\_tmpa\_tl} \\ % \sclobber & \sdir & \texttt{l\_tmpb\_tl} \\ % \end{sideeffects} % \begin{args} % 1 & \ain & tl variable to parse \\ % - & \aout & \texttt{l\_@@\_low\_tl} \\ % - & \aout & \texttt{l\_@@\_high\_tl} \\ % - & \aout & \texttt{l\_@@\_low\_x\_int} \\ % - & \aout & \texttt{l\_@@\_low\_y\_int} \\ % - & \aout & \texttt{l\_@@\_high\_x\_int} \\ % - & \aout & \texttt{l\_@@\_high\_y\_int} \\ % - & \aout & \texttt{l\_@@\_kind\_tl} \\ % - & \aout & \texttt{l\_@@\_gap\_count\_int} \\ % \end{args} % \begin{macrocode} \cs_new_protected:Npn \@@_parse_core:n #1 { % \end{macrocode} % Clear all variables which potentially are not reset to avoid leaking data from the previous parse % \begin{macrocode} \tl_clear:N \l_@@_low_tl \tl_clear:N \l_@@_high_tl \int_zero:N \l_@@_low_x_int \int_zero:N \l_@@_low_y_int \int_zero:N \l_@@_high_x_int \int_zero:N \l_@@_high_y_int % \end{macrocode} % Some fast-path checks % \begin{macrocode} \tl_set:Ne \l_tmpa_tl { \tl_trim_spaces:n {#1} } \tl_if_eq:NnTF \l_tmpa_tl { x } { \tl_set:Nn \l_@@_kind_tl { gap } \int_set:Nn \l_@@_gap_count_int { 1 } } % \end{macrocode} % Start actual parsing/splitting % \begin{macrocode} { \seq_set_split:NnV \l_tmpa_seq { - } \l_tmpa_tl \int_compare:nNnTF { \seq_count:N \l_tmpa_seq } = { 1 } { % \end{macrocode} % Atom % \begin{macrocode} \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 1 } } \regex_if_match:NVTF \c_hexdumptikz_common_hex_x_regex \l_tmpa_tl { \tl_set:Nn \l_@@_kind_tl { addr } \@@_parse_addr:NNN \l_tmpa_tl \l_@@_low_x_int \l_@@_low_tl \tl_set_eq:NN \l_@@_high_tl \l_@@_low_tl \int_set_eq:NN \l_@@_high_x_int \l_@@_low_x_int } { \regex_if_match:NVTF \c_hexdumptikz_common_idx_regex \l_tmpa_tl { \tl_set:Nn \l_@@_kind_tl { idx } \@@_parse_coord:NNN \l_tmpa_tl \l_@@_low_x_int \l_@@_low_y_int \int_set_eq:NN \l_@@_high_x_int \l_@@_low_x_int \int_set_eq:NN \l_@@_high_y_int \l_@@_low_y_int } { \msg_critical:nnV { hexdumptikz-selector } { invalid-segment-input } \l_tmpa_tl } } } { % \end{macrocode} % Range % \begin{macrocode} \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 1 } } \tl_set:Ne \l_tmpb_tl { \seq_item:Nn \l_tmpa_seq { 2 } } \regex_if_match:NVTF \c_hexdumptikz_common_hex_x_regex \l_tmpa_tl { % \end{macrocode} % Address range % \begin{macrocode} \regex_if_match:NVF \c_hexdumptikz_common_hex_x_regex \l_tmpb_tl { \msg_critical:nnV { hexdumptikz-selector } { invalid-addr-input } \l_tmpb_tl } \tl_set:Nn \l_@@_kind_tl { addr } \@@_parse_addr:NNN \l_tmpa_tl \l_@@_low_x_int \l_@@_low_tl \@@_parse_addr:NNN \l_tmpb_tl \l_@@_high_x_int \l_@@_high_tl } { % \end{macrocode} % Check if its really a valid index range % \begin{macrocode} \regex_if_match:NVF \c_hexdumptikz_common_idx_regex \l_tmpa_tl { \msg_critical:nnV { hexdumptikz-selector } { invalid-segment-input } \l_tmpa_tl } \regex_if_match:NVF \c_hexdumptikz_common_idx_regex \l_tmpb_tl { \msg_critical:nnV { hexdumptikz-selector } { invalid-idx-input } \l_tmpb_tl } % \end{macrocode} % Index range % \begin{macrocode} \tl_set:Nn \l_@@_kind_tl { idx } \@@_parse_coord:NNN \l_tmpa_tl \l_@@_low_x_int \l_@@_low_y_int \@@_parse_coord:NNN \l_tmpb_tl \l_@@_high_x_int \l_@@_high_y_int } } } } \cs_generate_variant:Nn \@@_parse_core:n { V } % \end{macrocode} % \end{fn} % % \subsection{Pushing to the context} % \begin{fn}{\@@_push_rule:N} % Push the parsed range / rule to the context % \begin{sideeffects} % \end{sideeffects} % \begin{args} % 1 & \ain & context to push to \\ % - & \ain & \texttt{l\_@@\_kind\_tl} \\ % - & \ain & \texttt{l\_@@\_low\_tl} \\ % - & \ain & \texttt{l\_@@\_high\_tl} \\ % - & \ain & \texttt{l\_@@\_low\_x\_int} \\ % - & \ain & \texttt{l\_@@\_low\_y\_int} \\ % - & \ain & \texttt{l\_@@\_high\_x\_int} \\ % - & \ain & \texttt{l\_@@\_high\_y\_int} \\ % - & \ain & \texttt{l\_@@\_style\_tl} \\ % - & \ain & \texttt{l\_@@\_pred\_idx\_tl} \\ % \end{args} % \begin{macrocode} \cs_new_protected:Npn \@@_push_rule:N #1 { % \end{macrocode} % rule storage % \begin{macrocode} \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { kind_seq } } \l_@@_kind_tl \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { addr_low_seq } } \l_@@_low_tl \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { addr_high_seq } } \l_@@_high_tl \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { idx_low_x_seq } } \l_@@_low_x_int \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { idx_low_y_seq } } \l_@@_low_y_int \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { idx_high_x_seq } } \l_@@_high_x_int \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { idx_high_y_seq } } \l_@@_high_y_int \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { style_seq } } \l_@@_style_tl \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { pred_idx_seq } } \l_@@_pred_idx_int } % \end{macrocode} % \end{fn} % % \begin{fn}{\@@_push_pred:N} % Push the parsed predicate to the context % \begin{sideeffects} % \end{sideeffects} % \begin{args} % 1 & \ain & context to push to \\ % - & \ain & \texttt{l\_@@\_pred\_kind\_tl} \\ % - & \ain & \texttt{l\_@@\_pred\_axis\_tl} \\ % - & \ain & \texttt{l\_@@\_pred\_mod\_int} \\ % - & \ain & \texttt{l\_@@\_pred\_res\_int} \\ % \end{args} % \begin{macrocode} \cs_new_protected:Npn \@@_push_pred:N #1 { % \end{macrocode} % pred storage % \begin{macrocode} \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { pred_kind_seq } } \l_@@_pred_kind_tl \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { pred_axis_seq } } \l_@@_pred_axis_tl \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { pred_mod_seq } } \l_@@_pred_mod_int \seq_gput_right:cV { \@@_ctx_var:nn { #1 } { pred_res_seq } } \l_@@_pred_res_int } % \end{macrocode} % \end{fn} % % \subsection{Public} % \begin{fn}{\hexdumptikz_selector_parse:Nn} % Parses a complete (c)list of rules. % \begin{sideeffects} % \sclobber & \sdir & \texttt{l\_tmpa\_seq} \\ % \sclobber & \sdir & \texttt{l\_tmpb\_seq} \\ % \sclobber & \sdir & \texttt{l\_tmpa\_tl} \\ % \sclobber & \sdir & \texttt{l\_tmpb\_tl} \\ % \end{sideeffects} % \begin{args} % 1 & \ain & context to push to \\ % 2 & \ain & tl to parse as comma separated list of rules \\ % \end{args} % \begin{macrocode} \cs_new_protected:Npn \hexdumptikz_selector_parse:Nn #1 #2 { % \end{macrocode} % First empty the context in terms of storage % \begin{macrocode} \hexdumptikz_selector_ctx_clear_storage:N #1 % \end{macrocode} % % Iterate over the whole (c)list of rules % \begin{macrocode} \clist_map_inline:nn { #2 } { \tl_set:Ne \l_tmpa_tl { \tl_trim_spaces:n {##1} } \tl_clear:N \l_@@_style_tl % \end{macrocode} % % Split off the style part if present % \begin{macrocode} \seq_set_split:NnV \l_tmpa_seq { -> } \l_tmpa_tl \int_compare:nNnT { \seq_count:N \l_tmpa_seq } > { 1 } { \tl_set:Ne \l_@@_style_tl { \seq_item:Nn \l_tmpa_seq { 2 } } } % \end{macrocode} % % Handle the rest % \begin{macrocode} \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 1 } } \tl_if_empty:NF \l_tmpa_tl { \tl_replace_all:Nee \l_tmpa_tl { \char_set_catcode_active:N | } { \char_set_catcode_other:N | } } \seq_set_split:Nne \l_tmpb_seq { | } { \l_tmpa_tl } \int_compare:nNnTF { \seq_count:N \l_tmpb_seq } = { 1 } { % \end{macrocode} % no predicate present % \begin{macrocode} \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpb_seq { 1 } } \int_zero:N \l_@@_pred_idx_int \@@_parse_core:V \l_tmpa_tl \@@_push_rule:N #1 } { % \end{macrocode} % one predicate present % % parse and push the predicate % \begin{macrocode} \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpb_seq { 2 } } \@@_parse_pred:V \l_tmpa_tl \@@_push_pred:N #1 % \end{macrocode} % % obtain the index of the just pushed predicate which needs to be referenced in the current rule. % \begin{macrocode} \int_set:Nn \l_@@_pred_idx_int { \seq_count:c { \@@_ctx_var:nn { #1 } { pred_kind_seq } } } % \end{macrocode} % % parse and push the rule % \begin{macrocode} \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpb_seq { 1 } } \@@_parse_core:V \l_tmpa_tl \@@_push_rule:N #1 } } } \cs_generate_variant:Nn \hexdumptikz_selector_parse:Nn { Ne } % \end{macrocode} % \end{fn} % \iffalse % % \fi % % \Finale