[Tex/LaTex] ifthenelse conditional & macro

conditionalsmacros

Why I get the error below if I use the

\ifthenelse{\boolean{@parts}}%
{%
}{}

conditional (defined into classicthesis.sty) you try to comment it, you don't have any error?

\documentclass[11pt]{scrreprt}

\usepackage[parts]{classicthesis}

\usepackage{expl3}

\ExplSyntaxOn

\newcommand{\useifmybooltrue}{

\RequirePackage{siunitx}
\RequirePackage{booktabs}
\RequirePackage{xparse}
\RequirePackage{environ}
\RequirePackage{geometry}

\bool_new:N \g_has_run_bool
\tl_new:N \l_aw_text_tl
\int_new:N \l_aw_tot_int
\int_new:N \g_aw_tot_alph_int
\int_new:N \g_wid_space_int
\int_new:N \g_space_int
\fp_new:N \g_rat_space_int
\fp_new:N \g_aw_avg_width_fp
\dim_new:N \myalphabetwidth
\dim_new:N \mytextwidth
\makeatletter
\input{\jobname.aux}
\tl_const:Nx \c_aw_the_alphabet_tl {abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.;?()!' \token_to_str:N :}

% this can be changed to an evironment or renamed or whatever
\NewDocumentCommand {\avgwidthstart} {}
  {
    \aw_avg_width:w
  }

\NewDocumentCommand {\avgwidthend}{}{}

% Here is the environment version, using just "text" as a name is probably a bad idea.
\NewEnviron{textcount}
{
  \expandafter\avgwidthstart\BODY\avgwidthend
}

\cs_new:Npn \aw_avg_width:w #1 \avgwidthend
  {
    % if first run, then generate variables to be used
    \bool_if:NF \g_has_run_bool
      {
        \tl_map_inline:Nn \c_aw_the_alphabet_tl
        {
          \int_new:c {g_##1_int}
          \fp_new:c {g_rat_##1_fp}
          \fp_new:c {g_wid_##1_fp}
        }
      }
    \tl_set:Nn \l_aw_text_tl {#1}

    % this can be used rather than the preceding line to take capital 
    % letters into account, but is Slooooooow
    %\tl_set:Nx \l_aw_text_tl {\tl_expandable_lowercase:n {#1}}

    \int_set:Nn \l_aw_tot_int {\tl_count:N \l_aw_text_tl}
    \tl_map_function:NN \c_aw_the_alphabet_tl \aw_get_counts:n
    \deal_with_spaces:n {#1}
    \tl_map_function:NN \c_aw_the_alphabet_tl \aw_calc_ratios:n
    \tl_map_function:NN \c_aw_the_alphabet_tl \aw_calc_avg_width:n
    \fp_gset_eq:NN \g_aw_avg_width_fp \l_tmpa_fp
    \fp_zero:N \l_tmpa_fp

    % the dimension \myalphabetwidth gives the width of the alphabet based on your character freq,
    % can be accessed by \the\myalphabetwidth
    \dim_gset:Nn \myalphabetwidth {\fp_to_dim:n {\fp_eval:n {61*\g_aw_avg_width_fp}}}

    % the dimension \mytextwidth gives the recommended \textwidth based on 66 chars per line.
    % can be accessed by \the\mytextwidth
    \dim_gset:Nn \mytextwidth {\fp_to_dim:n {\fp_eval:n {66*\g_aw_avg_width_fp}}}
    \protected@write\@mainaux{}{\mytextwidth=\the\mytextwidth}
    \bool_gset_true:N \g_has_run_bool

    % and lastly print the content
    #1
  }

\makeatother

\cs_new:Npn \aw_get_counts:n #1
  {
    % make a temporary token list from the document body 
    \tl_set_eq:NN \l_tmpb_tl \l_aw_text_tl
    % remove all occurrences of the character
    \tl_remove_all:Nn \l_tmpb_tl {#1}
    % add to appropriate int the number of occurrences of that character in current block
    \int_set:Nn \l_tmpa_int {\int_eval:n{\l_aw_tot_int -\tl_count:N \l_tmpb_tl}}
    % add to appropriate int the number of occurrences of that character in current block
    \int_gadd:cn {g_#1_int} {\l_tmpa_int}
    % add this to the total
    \int_gadd:Nn \g_aw_tot_alph_int {\l_tmpa_int}
  }

\cs_new:Npn \deal_with_spaces:n #1
  {
    \tl_set:Nn \l_tmpa_tl {#1}
    % rescan body with spaces as characters
    \tl_set_rescan:Nnn \l_tmpb_tl {\char_set_catcode_letter:N \ }{#1}
    % find number of new characters introduced.  add to number of spaces and alph chars
    \int_set:Nn \l_tmpa_int {\tl_count:N \l_tmpb_tl -\tl_count:N \l_tmpa_tl}
    \int_gadd:Nn \g_space_int {\l_tmpa_int}
    \int_gadd:Nn \g_aw_tot_alph_int {\l_tmpa_int}
    % since this comes after the rest of chars are dealt with, tot_alph is final total
    \fp_set:Nn \g_rat_space_fp {\g_space_int/\g_aw_tot_alph_int}
    % get width of space and use it.  obviously space is stretchable, so i'll assume
    % that the expansions and contractions cancel one another over large text.  is this
    % a terrible assumption???
    \hbox_set:Nn \l_tmpa_box {\ }
    \fp_gset:Nn \g_wid_space_fp {\dim_to_fp:n {\box_wd:N \l_tmpa_box}}
    \fp_add:Nn \l_tmpa_fp {\g_wid_space_fp*\g_rat_space_fp}
  }

\cs_new:Npn \aw_calc_ratios:n #1
  {
    % divide number of occurrences of char by total alphabetic chars
    \fp_gset:cn {g_rat_#1_fp}{{\int_use:c {g_#1_int}}/\g_aw_tot_alph_int}
  }

\cs_new:Npn \aw_calc_avg_width:n #1
  {
    % only need to find char widths once
    \bool_if:NF \g_has_run_bool
      {
        % find width of char box
        \hbox_set:Nn \l_tmpa_box {#1}
        \fp_gset:cn {g_wid_#1_fp}{\dim_to_fp:n {\box_wd:N \l_tmpa_box}}
      }
    % multiply it by char frequency and add to avg width
    \fp_add:Nn \l_tmpa_fp {{\fp_use:c {g_wid_#1_fp}}*{\fp_use:c {g_rat_#1_fp}}}
  }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% This part is just for fun. Delete it and the showtable command from the document if
% it isn't wanted
\tl_new:N \l_aw_tab_rows_tl
\seq_new:N \g_aw_the_alphabet_seq

\NewDocumentCommand {\showtable}{}
    {
      \clearpage
      \thispagestyle{empty}
      \newgeometry{top=2cm,bottom=2cm,left=2cm,right=2cm,heightrounded}
      \aw_make_table:
      \restoregeometry
    }

\cs_generate_variant:Nn \seq_set_split:Nnn {NnV}
\cs_new:Npn \aw_make_table:
    {
      \thispagestyle{empty}
      \seq_set_split:NnV \g_aw_the_alphabet_seq {} \c_aw_the_alphabet_tl
      \seq_map_function:NN \g_aw_the_alphabet_seq \aw_generate_row:n
    \begin{table}
        \centering
        \small
        \sisetup{round-mode = places,round-precision = 5,output-decimal-marker={,},table-format = 3.5}
      \begin{tabular}{lll}
        \toprule
        {Average\,\texttt{\textbackslash textwidth}}&{Average\,character\,width}&{Average\,alphabet\,width}\\
        \midrule
        \the\mytextwidth&\fp_eval:n {round(\g_aw_avg_width_fp,5)}pt&\the\myalphabetwidth\\
        \bottomrule
      \end{tabular}\par
    \end{table}
        \vfil
        \small
      \begin{center}
        Characters\,=\,\fp_eval:n {\g_aw_tot_alph_int}
      \end{center}
        \[%
        \mathrm{Line\,of\,types}=\frac{\fp_eval:n {\g_aw_tot_alph_int}\cdot\fp_eval:n {round(\g_aw_avg_width_fp,5)}\mathrm{pt}}%
                                             {\mathrm{\the\mytextwidth}}%
                                       =\fp_eval:n {round(\g_aw_tot_alph_int*{round(\g_aw_avg_width_fp,5)}/\mytextwidth,5)}
        \]
        \vfil
    \begin{table}
        \centering
        \small
        \sisetup{round-mode = places,round-precision = 5,output-decimal-marker={,},table-format = 3.5}
      \begin{tabular}{cS}
        \toprule
        {Letter}&{Actual}\\
        \midrule
        \textvisiblespace&\fp_eval:n {\g_rat_space_fp*100}\%\\
        \tl_use:N \l_aw_tab_rows_tl
        \bottomrule
      \end{tabular}\par
    \end{table}
    }

\cs_new:Npn \aw_generate_row:n #1
    {
       \tl_put_right:Nn \l_aw_tab_rows_tl {#1&}
       \tl_put_right:Nx \l_aw_tab_rows_tl {\fp_eval:n {100*{\fp_use:c {g_rat_#1_fp}}}\%}
       \tl_put_right:Nn \l_aw_tab_rows_tl {\\}
    }
}
\ExplSyntaxOff
\ifthenelse{\boolean{@parts}}{\useifmybooltrue}

\begin{document}

\avgwidthstart
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla eu purus eros. Aenean scelerisque fermentum nisi, ut tincidunt est viverra ac. Proin diam eros, ultrices sit amet aliquam at, accumsan sit amet magna. Integer nec nunc tincidunt metus scelerisque lacinia quis eget diam. Sed quis purus eros. Phasellus molestie dolor placerat lectus imperdiet tincidunt. Praesent aliquet metus eu elit ornare viverra. Ut nulla elit, convallis id adipiscing facilisis, laoreet et eros.
\avgwidthend

\end{document}

Error

Undefined control sequence }{}

! Undefined control sequence.
<argument> ...etry} \par \ExplSyntaxOn \par \bool
_new:N \g _has_run_bool \t...
l.208 }{}
^^M
The control sequence at the end of the top line

Best Answer

Your problem is a class beginners issue: you've tokenized your input using the conditional. Your set up is pretty hard to read, but the relevant part is

\ifthenelse{\boolean{@parts}}%
{%

\RequirePackage{siunitx}
\RequirePackage{booktabs}
\RequirePackage{xparse}
\RequirePackage{environ}
\RequirePackage{geometry}

% **********************************************************
\ExplSyntaxOn
% **********************************************************

\bool_new:N \g_has_run_bool

The key problem can be seen if we cut it down to

\ifthenelse{\boolean{@parts}}%
{%
\ExplSyntaxOn
\bool_new:N \g_has_run_bool

What happens here is that TeX reads all of the argument to \ifthenelse with the currently active catcodes: those of a normal LaTeX document. It does not matter that you set \ExplSyntaxOn as \bool_new:N has already been read with _ as a 'subscript' character. Thus TeX sees \bool followed by the tokens _, n, e, : and N.

You should never try to change the catcode of material 'inside' an argument: you always define 'outside'. That could be done by first creating the commands

\usepackage{expl3}
\ExplSyntaxOn
\newcommand { \useifmybooltrue } {
  % Code here
}
\ExplSyntaxOff
\ifthenelse{\boolean{@parts}}{\useifmybooltrue}...

or by using a TeX conditional, which do not absorb arguments

\makeatletter
\if@parts
  % Code here
\fi
Related Question