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}
14
  • 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 at 22:36
  • 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 at 0: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 at 1:34
  • 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 at 8:16
  • 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 9 at 20:03

1 Answer 1

3
+500

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}
4
  • 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 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. Mar 12 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 at 14:32
  • 1
    @projetmbc Great! Thank you! Mar 13 at 20:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .