2

I want to store several pieces of data, each of the following type, so that I can manipulate them both for formatting and for calculations. The number of lines analysed in the user's code is not fixed in advance.

I have already made the parser that extracts this values.

LINE: 1
    CTXT: xvals
    LABEL: t
    VAL: 1, 20, 300, 4000

LINE: 2 CTXT: imgs LABEL: f(t) VAL: a , b, bb, ccc, dddd

LINE: 3 CTXT: imgs LABEL: g(t) VAL: U, V, VV, WWW, XXXX

I will produce in this simple situation the following output, see the nicematrix table, but this is only the 1st functionality. I would like for example to compute the maximum of f later in the document. To do that, I need to recover the context imgs such as to find the label f(t) and its associated values.

\documentclass[11pt, a4paper]{article}

\usepackage{nicematrix}

\begin{document}

\section{What the user types}

\begin{verbatim} \begin{functable} xvals = t : 1 , 20 , 300 , 4000 ; imgs = f(t) : a , bb , ccc , dddd ; g(t) : U , VV , WWW , XXXX \end{functable} \end{verbatim}

\section{What the user sees}

$\begin{NiceArray}[cell-space-limits = 3pt]{c*{4}{|c}} t & 1 & 20 & 300 & 4000 \ \hline f(t) & a & bb & ccc & dddd \ \hline g(t) & U & VV & VVV & WWW \end{NiceArray}$

\section{What can be do after}

The user can ask for example to rebuild the 1st table with something like this.

\begin{verbatim} \begin{functable}[rebuild=1] \end{functable} \end{verbatim}

The user can also ask to calculate the maximum value of $f(t)$ via something like \verb+\calcfunctable{max=f(t)}+.

\end{document}

I see two possibilities.

  1. Either use the starray package, but I fear that this project will be not maintained in the long term.

  2. Or I can try to implement a homemade solution which looks like a list of property lists.

JSON like structure expected [2024-03-09.v1]

The idea is to have a variable associated with each table, a variable that can be iterated dictionary by dictionary, and for each dictionary thus obtained, we will have access to the different types of values.

[
    {
        'CTX'  : 'xvals',
        'VAL'  : ['1', '20', '300', '4000'],
        'LABEL': 't',
        'LINE' : '1'
    },
    {
        'CTX'  : 'imgs',
        'VAL'  : ['a' , 'b', 'bb', 'ccc', 'dddd'],
        'LABEL': 'f(t)',
        'LINE' : '2'
    },
    {
        'CTX'  : 'imgs',
        'VAL'  : ['U', 'V', 'VV', 'WWW', 'XXXX'],
        'LABEL': 'g(t)',
        'LINE' : '3'
    }
]

Some additional information [2024-01-17.v1]

  1. The parser gives l3-token lists.

  2. The value of VAL keys can be used as comma separated lists, or as sequences by L3 functions. The second choice should be better.

  3. The values will be used in different situations, sometimes to print a table, sometimes to perform calculations. This should be similar to what it is done in this answer.

Proof of Concept simplified [2024-03-09.v1]

\documentclass[12pt]{article}

\ExplSyntaxOn

% -- FUNCTABLE ENV. -- %

\NewDocumentEnvironment{ functable }{ b }{% \tns_functab_functable:n { #1 } }{}

\cs_new:Npn \tns_functab_functable:n #1 { \tns_core_DSL_ctxt_parser:nn { functable } { #1 }

\bigskip\par NEW ~ DATA ~ \int_use:N \g_tns_functab_id_int

\par TODO }

% -- DSL - L3 -- %

\tl_new:N \g_tns_functab_semicolon_tl \tl_gset:Nn \g_tns_functab_semicolon_tl { ; }

\tl_new:N \g_tns_functab_colon_tl \tl_gset:Nn \g_tns_functab_colon_tl { : }

\cs_generate_variant:Nn \seq_set_split:Nnn { NV }

\AtBeginDocument { \tl_gset_rescan:NnV \g_tns_functab_semicolon_tl {} \g_tns_functab_semicolon_tl

\tl_gset_rescan:NnV \g_tns_functab_colon_tl {} \g_tns_functab_colon_tl }

% :: AGNOSTIC PARSERS :: %

\int_new:N \g_tns_functab_id_int \int_gset:Nn \g_tns_functab_id_int { 0 }

\int_new:N \l_tns_core_ctxt_nb_line_int \seq_new:N \l_tns_core_ctxts_seq \tl_new:N \l_tns_core_current_ctxt_tl

\cs_new:Npn \tns_core_DSL_ctxt_parser:nn #1#2 { % The ID nb. of the env. \int_gincr:N \g_tns_functab_id_int

% Line by line parsing. % % Lines are semi-colon separated. \seq_set_split:NVn \l_tns_core_ctxts_seq \g_tns_functab_semicolon_tl { #2 }

\int_zero:N \l_tns_core_ctxt_nb_line_int

\seq_map_inline:Nn \l_tns_core_ctxts_seq { \bigskip\par

\int_incr:N \l_tns_core_ctxt_nb_line_int

Line ~ \int_use:N \l_tns_core_ctxt_nb_line_int
\par

% Get the context and its content. \seq_set_split:Nnn \l_tmpa_seq { = } { ##1 }

\int_set:Nn \l_tmpa_int {\seq_count:N \l_tmpa_seq}

\quad
\int_case:nnF { \int_use:N \l_tmpa_int } {
  { 1 } {
    LAST - CTXT: ~
    (\tl_use:N \l_tns_core_current_ctxt_tl)
  }
  { 2 } {
    \seq_pop_left:NN \l_tmpa_seq \l_tns_core_current_ctxt_tl

    NEW - CTXT: ~
    (\tl_use:N \l_tns_core_current_ctxt_tl)
  }
}{
  ILLEGAL!
}

% Get the optional label and its content. \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl

\seq_set_split:NVV \l_tmpa_seq
                   \g_tns_functab_colon_tl
                   \l_tmpa_tl

\int_set:Nn \l_tmpa_int {\seq_count:N \l_tmpa_seq}

\par\quad
\int_case:nnF { \int_use:N \l_tmpa_int } {
  { 1 } {
    NO LABEL
  }
  { 2 } {
    \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl

    LABEL: ~
    $(\tl_use:N \l_tmpa_tl)$
  }
}{
  ILLEGAL!
}

\par\quad

\seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl

VAL: ~
$(\tl_use:N \l_tmpa_tl)$

} }

\ExplSyntaxOff

\begin{document}

\begin{functable} xvals = t : 1 , 20 , 300 , 4000 ; imgs = f(t) : a , bb , ccc , dddd ; g(t) : U , VV , WWW , XXXX \end{functable}

\end{document}

projetmbc
  • 13,315
  • 3
    So each "Line" here would be a single record containing three key/value pairs? Is there some pattern to the separators used in the VAL parts (looks like a mix of ,, :, and ;.) – Alan Munn Jan 15 '24 at 22:36
  • Each value must be stored as a token list. Other function will work with this. I have made the parser that catches this values. – projetmbc Jan 15 '24 at 22:38
  • 1
    impossible to say in this generality as how to optimise the data structure depends on what you want to do with the data, but possibly \def\linei{{xvals}{t}{(1, 20, 300, 4000)}} \def\liniii{{imgs}{f(t)}{(a ; b, bb, ccc, dddd)}} then for example \def\listval#1{\expandafter\expandafter\expandafter\@thirdofthree\csname list\romanumeral#1\endcsname} so \listval{2} expands to (a ; b, bb, ccc, dddd) – David Carlisle Jan 15 '24 at 22:44
  • 4
    If the order matters, maybe use a sequence or sequences? But it is really unclear to me what you're trying to do. We don't know how you plan to have the values used, for example, or what format your parser provides. – cfr Jan 16 '24 at 00:06
  • 2
    The general coding advice I've come to appreciate more and more is that the sooner you get your data into the format you eventually want it to be, the easier your life will be. In that case, how are you going to use this data? – Teepeemm Jan 16 '24 at 01:34
  • @cfr I have updated my question: see the end of my post. If needed, I can clarify my explanations. – projetmbc Jan 16 '24 at 07:57
  • @Teepeemm I have updated my question: see the end of my post. If needed, I can clarify my explanations. – projetmbc Jan 16 '24 at 07:58
  • 4
    If you really want some good coding advice with us optimising anything, I suggest you provide at least the function-signatures with input and output specification of your accessor functions, and (relative to each other) how often you expect the different accessors to be needed. That way we can give tips on your accessors and the underlying data structures. Also, a rough estimate as to how many such points you need could influence the suggested structures. Do you need (some of) the accessors to be fully expandable or is unexpandable fine? – Skillmon Jan 16 '24 at 08:16
  • Probably a dumb question, but what's 'PoC'? 'Piece of Code'? 'Plotting our Conquest'? 'Packet of Crisps'? 'Parcel of Conundrums'? – cfr Jan 16 '24 at 21:02
  • 1
    @cfr PoC = Proof of Concept – projetmbc Mar 09 '24 at 17:07
  • It's still not clear to me how you're going to use your data. What should happen with the data? And don't you know which lines are going to be formatted and which are going to be calculated? – Teepeemm Mar 09 '24 at 18:54
  • @Teepeemm I have added a MWE with a piece of code that I would like to build. – projetmbc Mar 09 '24 at 19:52
  • 2
    Off-topic: \int_set:Nn \g_tns_functab_id_int { 0 } is wrong. If it is global, use gset. If it isn't, use \l_tns_functab_id_int. On topic: but what is it supposed to build? It's not clear what you want the result to be. I know you want something, but I really have no idea what. – cfr Mar 09 '24 at 20:03
  • @cfr Good point. I have added a fake example of use. See the first pseudo MWE. – projetmbc Mar 09 '24 at 20:11

1 Answers1

3

Update

The code is split over an environment savefunctable and a command \usefunctable. Both take a name as the first argument. The contents of the environment savefunctable is stored internally and can be used later with the command \usefunctable by specifying the appropriate name.

enter image description here

\documentclass[border=6pt,varwidth]{standalone}
\usepackage{nicematrix}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\ExplSyntaxOn
\cs_generate_variant:Nn \seq_map_indexed_inline:Nn { cn }
\cs_generate_variant:Nn \seq_gset_from_clist:Nn { ce }
\seq_new:N \l__projetmbc_functable_seq
\seq_new:N \l__projetmbc_key_name_list_seq
\tl_new:N \l__projetmbc_coordinates_tl
\tl_new:N \l__projetmbc_key_tl
\tl_new:N \l__projetmbc_name_tl
\NewDocumentEnvironment { savefunctable } { m +b }
  {
    \seq_set_split:Nnn \l__projetmbc_functable_seq { ; } {#2}
    \seq_map_inline:Nn \l__projetmbc_functable_seq
      {
        \seq_set_split:Nnn \l__projetmbc_key_name_list_seq { = } {##1}
        \int_compare:nNnT { \seq_count:N \l__projetmbc_key_name_list_seq } = { 2 }
          {
            \seq_pop_left:NN \l__projetmbc_key_name_list_seq \l__projetmbc_key_tl
            \seq_gclear_new:c { g__projetmbc_#1_key_\l__projetmbc_key_tl _seq }
          }
        \seq_set_split:Nee \l__projetmbc_key_name_list_seq { \c_colon_str } { \seq_item:Nn \l__projetmbc_key_name_list_seq { 1 } }
        \seq_pop_left:NN \l__projetmbc_key_name_list_seq \l__projetmbc_name_tl
        \seq_gput_right:cV { g__projetmbc_#1_key_\l__projetmbc_key_tl _seq } \l__projetmbc_name_tl
        \seq_gclear_new:c { g__projetmbc_#1_key_\l__projetmbc_key_tl _name_\l__projetmbc_name_tl _seq }
        \seq_gset_from_clist:ce { g__projetmbc_#1_key_\l__projetmbc_key_tl _name_\l__projetmbc_name_tl _seq } { \seq_item:Nn \l__projetmbc_key_name_list_seq { 1 } }
      }
  }
  {}
\NewDocumentCommand \usefunctable { m m }
  {
    \str_case:nnF {#2}
      {
        { list~of~imgs }
          {
            \seq_use:cn { g__projetmbc_#1_key_imgs_seq } { , ~ }
          }
        { maximum~of~xvals }
          {
            \fp_eval:n { max ( \seq_use:cn { g__projetmbc_#1_key_xvals_name_\seq_item:cn { g__projetmbc_#1_key_xvals_seq } { 1 }_seq } { , } ) }
          }
        { NiceArray }
          {
            $
              \begin { NiceArray }
                [ cell-space-limits = 3pt ]
                { c * { \seq_count:c { g__projetmbc_#1_key_xvals_name_\seq_item:cn { g__projetmbc_#1_key_xvals_seq } { 1 }_seq } } { | c } }
                \seq_item:cn { g__projetmbc_#1_key_xvals_seq } { 1 } & \seq_use:cn { g__projetmbc_#1_key_xvals_name_\seq_item:cn { g__projetmbc_#1_key_xvals_seq } { 1 }_seq } { & } \\ \hline
                \seq_map_indexed_inline:cn { g__projetmbc_#1_key_imgs_seq }
                  {
                    ##2 & \seq_use:cn { g__projetmbc_#1_key_imgs_name_##2_seq } { & }
                    \int_compare:nNnF {##1} = { \seq_count:c { g__projetmbc_#1_key_imgs_seq } }
                      { \\ \hline }
                  }
              \end { NiceArray }
            $
          }
        { plot }
          {
            \tl_build_begin:N \l__projetmbc_coordinates_tl
            \seq_map_indexed_inline:cn { g__projetmbc_#1_key_xvals_name_\seq_item:cn { g__projetmbc_#1_key_xvals_seq } { 1 }_seq }
              {
                \tl_build_put_right:Ne \l__projetmbc_coordinates_tl
                  { ( ##2 , \seq_item:cn { g__projetmbc_#1_key_imgs_name_\seq_item:cn { g__projetmbc_#1_key_imgs_seq } { 1 }_seq } {##1} ) }
              }
            \tl_build_end:N \l__projetmbc_coordinates_tl
            \begin { tikzpicture }
              \begin { axis }
                [
                  ymin = 0
                ]
                \addplot coordinates
                  { \l__projetmbc_coordinates_tl }
                ;
              \end { axis }
            \end { tikzpicture }
          }
      }
      { \errmessage { wrong~option~for~functable~environment } }
  }
\ExplSyntaxOff
\begin{document}
\begin{savefunctable}{example 1}
    xvals =    t : 1 , 20 , 300 , 4000 ;
    imgs  = f(t) : a , bb , ccc , dddd ;
            g(t) : U , VV , WWW , XXXX
\end{savefunctable}
\begin{savefunctable}{example 2}
    xvals =    t : 1 , 2 , 3 , 4 ;
    imgs  = f(t) : 8 , 5 , 7 , 6 ;
\end{savefunctable}

Using \texttt{example 1} with \texttt{NiceArray}: \usefunctable{example 1}{NiceArray}

The maximum of the \texttt{xvals} of \texttt{example 2}: \usefunctable{example 2}{maximum of xvals}

The list of \texttt{imgs} of \texttt{example 1}: \usefunctable{example 1}{list of imgs}

A \texttt{plot} for \texttt{example 2}: \usefunctable{example 2}{plot} \end{document}

Original answer

An environment functable is defined. It takes one mandatory argument and thereafter the body.

The body is first split over ; with \seq_set_split:Nnn. Then items are split over =. A key such as xvals or imgs is stored in \l__projetmbc_key_tl.

Similarly, a name such as t or f(t) is stored in \l__projetmbc_name_tl.

The environment functable depends on the first mandatory argument. Below are examples for NiceArray, maximum of xvals, list of imgs and plot.

enter image description here

\documentclass[border=6pt,varwidth]{standalone}
\usepackage{nicematrix}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\ExplSyntaxOn
\cs_generate_variant:Nn \seq_map_indexed_inline:Nn { cn }
\cs_generate_variant:Nn \seq_set_from_clist:Nn { ce }
\seq_new:N \l__projetmbc_functable_seq
\seq_new:N \l__projetmbc_key_name_list_seq
\tl_new:N \l__projetmbc_coordinates_tl
\tl_new:N \l__projetmbc_key_tl
\tl_new:N \l__projetmbc_name_tl
\NewDocumentEnvironment { functable } { m +b }
  {
    \seq_set_split:Nnn \l__projetmbc_functable_seq { ; } {#2}
    \seq_map_inline:Nn \l__projetmbc_functable_seq
      {
        \seq_set_split:Nnn \l__projetmbc_key_name_list_seq { = } {##1}
        \int_compare:nNnT { \seq_count:N \l__projetmbc_key_name_list_seq } = { 2 }
          {
            \seq_pop_left:NN \l__projetmbc_key_name_list_seq \l__projetmbc_key_tl
            \seq_clear_new:c { l__projetmbc_key_\l__projetmbc_key_tl _seq }
          }
        \seq_set_split:Nee \l__projetmbc_key_name_list_seq { \c_colon_str } { \seq_item:Nn \l__projetmbc_key_name_list_seq { 1 } }
        \seq_pop_left:NN \l__projetmbc_key_name_list_seq \l__projetmbc_name_tl
        \seq_put_right:cV { l__projetmbc_key_\l__projetmbc_key_tl _seq } \l__projetmbc_name_tl
        \seq_clear_new:c { l__projetmbc_key_\l__projetmbc_key_tl _name_\l__projetmbc_name_tl _seq }
        \seq_set_from_clist:ce { l__projetmbc_key_\l__projetmbc_key_tl _name_\l__projetmbc_name_tl _seq } { \seq_item:Nn \l__projetmbc_key_name_list_seq { 1 } }
      }
    \str_case:nnF {#1}
      {
        { list~of~imgs }
          {
            \seq_use:Nn \l__projetmbc_key_imgs_seq { , ~ }
          }
        { maximum~of~xvals }
          {
            \fp_eval:n { max ( \seq_use:cn { l__projetmbc_key_xvals_name_\seq_item:Nn \l__projetmbc_key_xvals_seq { 1 }_seq } { , } ) }
          }
        { NiceArray }
          {
            $
              \begin { NiceArray }
                [ cell-space-limits = 3pt ]
                { c * { \seq_count:c { l__projetmbc_key_xvals_name_\seq_item:Nn \l__projetmbc_key_xvals_seq { 1 }_seq } } { | c } }
                \seq_item:Nn \l__projetmbc_key_xvals_seq { 1 } & \seq_use:cn { l__projetmbc_key_xvals_name_\seq_item:Nn \l__projetmbc_key_xvals_seq { 1 }_seq } { & } \\ \hline
                \seq_map_indexed_inline:Nn \l__projetmbc_key_imgs_seq
                  {
                    ##2 & \seq_use:cn { l__projetmbc_key_imgs_name_##2_seq } { & }
                    \int_compare:nNnF {##1} = { \seq_count:N \l__projetmbc_key_imgs_seq }
                      { \\ \hline }
                  }
              \end { NiceArray }
            $
          }
        { plot }
          {
            \tl_build_begin:N \l__projetmbc_coordinates_tl
            \seq_map_indexed_inline:cn { l__projetmbc_key_xvals_name_\seq_item:Nn \l__projetmbc_key_xvals_seq { 1 }_seq }
              {
                \tl_build_put_right:Ne \l__projetmbc_coordinates_tl
                  { ( ##2 , \seq_item:cn { l__projetmbc_key_imgs_name_\seq_item:Nn \l__projetmbc_key_imgs_seq { 1 }_seq } {##1} ) }
              }
            \tl_build_end:N \l__projetmbc_coordinates_tl
            \begin { tikzpicture }
              \begin { axis }
                [
                  ymin = 0
                ]
                \addplot coordinates
                  { \l__projetmbc_coordinates_tl }
                ;
              \end { axis }
            \end { tikzpicture }
          }
      }
      { \errmessage { wrong~option~for~functable~environment } }
  }
  {}
\ExplSyntaxOff
\begin{document}
Using \texttt{NiceArray}:

\begin{functable}{NiceArray} xvals = t : 1 , 20 , 300 , 4000 ; imgs = f(t) : a , bb , ccc , dddd ; g(t) : U , VV , WWW , XXXX \end{functable}

The maximum of the \texttt{xvals}: \begin{functable}{maximum of xvals} xvals = t : 1 , 20 , 300 , 4000 ; imgs = f(t) : a , bb , ccc , dddd ; g(t) : U , VV , WWW , XXXX \end{functable}

The list of \texttt{imgs}: \begin{functable}{list of imgs} xvals = t : 1 , 20 , 300 , 4000 ; imgs = f(t) : a , bb , ccc , dddd ; g(t) : U , VV , WWW , XXXX \end{functable}

A plot: \begin{functable}{plot} xvals = t : 1 , 2 , 3 , 4 ; imgs = f(t) : 8 , 5 , 7 , 6 ; \end{functable} \end{document}

matexmatics
  • 4,819
  • Thanks for the suggestion. Unfortunately, my problem lies in storing the content once it's been analysed, so that I don't have to parse the content every time, to avoid retyping the same content over and over again. – projetmbc Mar 12 '24 at 19:40
  • 1
    @projetmbc In the updated answer, the code is split over an environment savefunctable and a command \usefunctable. Both take a name as the first argument. The contents of the environment savefunctable is stored internally and can be used later with the command \usefunctable by specifying the appropriate name. – matexmatics Mar 12 '24 at 22:30
  • Thanks for the updated answer. I will test it in my brand-new package. I will cite this answer. – projetmbc Mar 13 '24 at 14:32
  • 1
    @projetmbc Great! Thank you! – matexmatics Mar 13 '24 at 20:57