14

I need to type truth tables for my computer architecture classes. Until now I was using a manually typed tabular but today I've found this answer from Scott H. which provides a command called truthtable to automatically typeset it.

A command like

\truthtable{a,b,c}{a+b;b*(-c);-(a+b)+(b*(-c))}

produces

enter image description here

Now I would like to change logical operators (\vee, \wedge and \neg) and use + (or), · (and) and \overline or \bar for not.

The function which typesets operators is:

\cs_new_protected:Npn \__tt_build_header:
    {
        \seq_set_from_clist:NN \l__tt_header_seq \l__tt_vars_clist
        \seq_concat:NNN \l__tt_header_seq \l__tt_header_seq \l__tt_exprs_seq
        \tl_set:Nx \l_tmpa_tl {\seq_use:Nnnn \l__tt_header_seq {&}{&}{&}}
        \tl_replace_all:Nnn \l_tmpa_tl {*} {\wedge}
        \tl_replace_all:Nnn \l_tmpa_tl {+} {\vee}
        \tl_replace_all:Nnn \l_tmpa_tl {->} {\to}
        \tl_replace_all:Nnn \l_tmpa_tl {-} {\neg}
        \tl_use:N \l_tmpa_tl
    }

Although it's written in LaTeX3 and I don't understand it, it's easy to replace \wedge with \cdot and \vee with +. But I don't know if it's easy to replace \neg to obtain a symbol (or expression) with an overline. As I imagine this alternative is difficult, a centred tilde ~ could be enough. Then I've tested with \sim but the result is ugly, the symbol is far away from negated variable or expression. Could you help me with a better alternative?

The complete code (needs lualatex) is

\documentclass{article}
\usepackage{xparse}

\begingroup
  \catcode`\%=12\relax
  \gdef\patmatch{"(%b())->(%b())","!%1||%2"}
\endgroup

\def\setimpaux#1{%
  \directlua{
    local s, _ = string.gsub("\luatexluaescapestring{#1}",\patmatch)
    tex.sprint(s)
  }
}

\ExplSyntaxOn
\int_new:N \l__tt_num_rows_int
\int_new:N \l__tt_num_cols_int
\int_new:N \l__tt_num_vars_int
\clist_new:N \l__tt_vars_clist
\seq_new:N \l__tt_exprs_seq
\seq_new:N \l__tt_header_seq

\NewDocumentCommand {\truthtable}{ m m }
    {
        \truth_table:nn {#1}{#2}
    }

\cs_new_protected:Npn \truth_table:nn #1#2
    {
        \clist_set:Nn \l__tt_vars_clist {#1}
        \seq_set_split:Nnn \l__tt_exprs_seq {;} {#2}
        \int_set:Nn \l__tt_num_vars_int {\clist_count:N \l__tt_vars_clist}
        \int_set:Nn \l__tt_num_rows_int {\fp_to_int:n {2^{\l__tt_num_vars_int}-1}}
        \int_set:Nn \l__tt_num_cols_int {\clist_count:N \l__tt_vars_clist +\seq_count:N \l__tt_exprs_seq}
        \__tt_gen_bins:
        \seq_map_function:NN \l__tt_exprs_seq \__tt_eval_bools:n
        \__tt_build_table:
    }

\cs_new_protected:Npn \__tt_build_header:
    {
        \seq_set_from_clist:NN \l__tt_header_seq \l__tt_vars_clist
        \seq_concat:NNN \l__tt_header_seq \l__tt_header_seq \l__tt_exprs_seq
        \tl_set:Nx \l_tmpa_tl {\seq_use:Nnnn \l__tt_header_seq {&}{&}{&}}
        \tl_replace_all:Nnn \l_tmpa_tl {*} {\wedge}
        \tl_replace_all:Nnn \l_tmpa_tl {+} {\vee}
        \tl_replace_all:Nnn \l_tmpa_tl {->} {\to}
        \tl_replace_all:Nnn \l_tmpa_tl {-} {\neg}
        \tl_use:N \l_tmpa_tl
    }

\cs_generate_variant:Nn \seq_use:Nnnn {cnnn}
\cs_new_protected:Npn \__tt_build_table:
    {
        \begin{array}{*{\int_use:N \l__tt_num_cols_int}{c}}
            \__tt_build_header:\\\hline
            \int_step_inline:nnnn {0}{1}{\l__tt_num_rows_int}
                {
                    \seq_use:cnnn {l__tt_row_{##1}_seq}{&}{&}{&}\\
                }
        \end{array}
    }

\cs_new_protected:Npn \__tt_set_imp:n #1
    {
        \tl_if_in:nnT {#1} {->}
            {
                \tl_set:Nx \l_tmpb_tl {\setimpaux{#1}}
                \exp_args:NV \__tt_set_imp:n \l_tmpb_tl
            }
    }
\cs_generate_variant:Nn \__tt_set_imp:n {V}

\cs_generate_variant:Nn \tl_replace_all:Nnn {Nnx}
\cs_new_protected:Npn \__tt_eval_bools:n #1
    {
        \tl_set:Nn \l_tmpa_tl {#1}
        \int_step_inline:nnnn {0}{1}{\l__tt_num_rows_int}
            {
                \int_set:Nn \l_tmpa_int {1}
                \tl_set_eq:NN \l_tmpb_tl \l_tmpa_tl
                \__tt_set_imp:V \l_tmpb_tl
                \tl_replace_all:Nnn \l_tmpb_tl {*}{&&}
                \tl_replace_all:Nnn \l_tmpb_tl {+}{||}
                \tl_replace_all:Nnn \l_tmpb_tl {-}{!}
                \clist_map_inline:Nn \l__tt_vars_clist
                    {
                        \tl_replace_all:Nnx \l_tmpb_tl {####1} {\seq_item:cn {l__tt_row_{##1}_seq} {\l_tmpa_int}}
                        \int_incr:N \l_tmpa_int
                    }
                \seq_put_right:cx {l__tt_row_{##1}_seq} {\fp_eval:n \l_tmpb_tl}
            }
    }

\cs_generate_variant:Nn \seq_set_split:Nnn {cnx}
\cs_new_protected:Npn \__tt_gen_bins:
    {
        \int_step_inline:nnnn {0}{1}{\l__tt_num_rows_int}
            {
                \seq_clear_new:c {l__tt_row_{##1}_seq}
                \seq_set_split:cnx {l__tt_row_{##1}_seq} {} {\int_to_binary:n {##1}}
                \int_while_do:nn {\seq_count:c {l__tt_row_{##1}_seq} < \l__tt_num_vars_int}
                    {
                        \seq_put_left:cn {l__tt_row_{##1}_seq} {0}
                    }
            }
    }

\ExplSyntaxOff
\begin{document}

\[
\truthtable{a,b,c}{a+b;b*(-c);-(a+b)+(b*(-c))}
\]

\end{document}
7
  • \mathord{\sim}? Feb 26, 2014 at 18:59
  • @GonzaloMedina. It works! What does \mathord do? It's the first time I see this command.
    – Ignasi
    Feb 26, 2014 at 19:07
  • \mathord makes the following character or subformula function as an ordinary object; as such, the spacing is that of an ordinary object instead of the default spacing of \sim as a relational symbol (\sim is defined using \mathrel). Feb 26, 2014 at 19:11
  • Are you taking the class or teaching it?
    – cfr
    Feb 21, 2016 at 21:15
  • @cfr I'm the teacher, but why your curiosity?
    – Ignasi
    Feb 21, 2016 at 21:41

4 Answers 4

12

The undesired spacing around \sim comes from the fact that \sim is defined as a relational symbol using \mathrel; from the kernel:

\DeclareMathSymbol{\sim}{\mathrel}{symbols}{"18}

You can supress this space treating \sim as an ordinary symbol using \mathord{\sim}:

\cs_new_protected:Npn \__tt_build_header:
    {
        \seq_set_from_clist:NN \l__tt_header_seq \l__tt_vars_clist
        \seq_concat:NNN \l__tt_header_seq \l__tt_header_seq \l__tt_exprs_seq
        \tl_set:Nx \l_tmpa_tl {\seq_use:Nnnn \l__tt_header_seq {&}{&}{&}}
        \tl_replace_all:Nnn \l_tmpa_tl {*} {\cdot}
        \tl_replace_all:Nnn \l_tmpa_tl {+} {+}
        \tl_replace_all:Nnn \l_tmpa_tl {->} {\to}
        \tl_replace_all:Nnn \l_tmpa_tl {-} {\mathord{\sim}}
        \tl_use:N \l_tmpa_tl
    }

the compete code:

\documentclass{article}
\usepackage{xparse}

\begingroup
  \catcode`\%=12\relax
  \gdef\patmatch{"(%b())->(%b())","!%1||%2"}
\endgroup

\def\setimpaux#1{%
  \directlua{
    local s, _ = string.gsub("\luatexluaescapestring{#1}",\patmatch)
    tex.sprint(s)
  }
}

\ExplSyntaxOn
\int_new:N \l__tt_num_rows_int
\int_new:N \l__tt_num_cols_int
\int_new:N \l__tt_num_vars_int
\clist_new:N \l__tt_vars_clist
\seq_new:N \l__tt_exprs_seq
\seq_new:N \l__tt_header_seq

\NewDocumentCommand {\truthtable}{ m m }
    {
        \truth_table:nn {#1}{#2}
    }

\cs_new_protected:Npn \truth_table:nn #1#2
    {
        \clist_set:Nn \l__tt_vars_clist {#1}
        \seq_set_split:Nnn \l__tt_exprs_seq {;} {#2}
        \int_set:Nn \l__tt_num_vars_int {\clist_count:N \l__tt_vars_clist}
        \int_set:Nn \l__tt_num_rows_int {\fp_to_int:n {2^{\l__tt_num_vars_int}-1}}
        \int_set:Nn \l__tt_num_cols_int {\clist_count:N \l__tt_vars_clist +\seq_count:N \l__tt_exprs_seq}
        \__tt_gen_bins:
        \seq_map_function:NN \l__tt_exprs_seq \__tt_eval_bools:n
        \__tt_build_table:
    }

\cs_new_protected:Npn \__tt_build_header:
    {
        \seq_set_from_clist:NN \l__tt_header_seq \l__tt_vars_clist
        \seq_concat:NNN \l__tt_header_seq \l__tt_header_seq \l__tt_exprs_seq
        \tl_set:Nx \l_tmpa_tl {\seq_use:Nnnn \l__tt_header_seq {&}{&}{&}}
        \tl_replace_all:Nnn \l_tmpa_tl {*} {\cdot}
        \tl_replace_all:Nnn \l_tmpa_tl {+} {+}
        \tl_replace_all:Nnn \l_tmpa_tl {->} {\to}
        \tl_replace_all:Nnn \l_tmpa_tl {-} {\mathord{\sim}}
        \tl_use:N \l_tmpa_tl
    }

\cs_generate_variant:Nn \seq_use:Nnnn {cnnn}
\cs_new_protected:Npn \__tt_build_table:
    {
        \begin{array}{*{\int_use:N \l__tt_num_cols_int}{c}}
            \__tt_build_header:\\\hline
            \int_step_inline:nnnn {0}{1}{\l__tt_num_rows_int}
                {
                    \seq_use:cnnn {l__tt_row_{##1}_seq}{&}{&}{&}\\
                }
        \end{array}
    }

\cs_new_protected:Npn \__tt_set_imp:n #1
    {
        \tl_if_in:nnT {#1} {->}
            {
                \tl_set:Nx \l_tmpb_tl {\setimpaux{#1}}
                \exp_args:NV \__tt_set_imp:n \l_tmpb_tl
            }
    }
\cs_generate_variant:Nn \__tt_set_imp:n {V}

\cs_generate_variant:Nn \tl_replace_all:Nnn {Nnx}
\cs_new_protected:Npn \__tt_eval_bools:n #1
    {
        \tl_set:Nn \l_tmpa_tl {#1}
        \int_step_inline:nnnn {0}{1}{\l__tt_num_rows_int}
            {
                \int_set:Nn \l_tmpa_int {1}
                \tl_set_eq:NN \l_tmpb_tl \l_tmpa_tl
                \__tt_set_imp:V \l_tmpb_tl
                \tl_replace_all:Nnn \l_tmpb_tl {*}{&&}
                \tl_replace_all:Nnn \l_tmpb_tl {+}{||}
                \tl_replace_all:Nnn \l_tmpb_tl {-}{!}
                \clist_map_inline:Nn \l__tt_vars_clist
                    {
                        \tl_replace_all:Nnx \l_tmpb_tl {####1} {\seq_item:cn {l__tt_row_{##1}_seq} {\l_tmpa_int}}
                        \int_incr:N \l_tmpa_int
                    }
                \seq_put_right:cx {l__tt_row_{##1}_seq} {\fp_eval:n \l_tmpb_tl}
            }
    }

\cs_generate_variant:Nn \seq_set_split:Nnn {cnx}
\cs_new_protected:Npn \__tt_gen_bins:
    {
        \int_step_inline:nnnn {0}{1}{\l__tt_num_rows_int}
            {
                \seq_clear_new:c {l__tt_row_{##1}_seq}
                \seq_set_split:cnx {l__tt_row_{##1}_seq} {} {\int_to_binary:n {##1}}
                \int_while_do:nn {\seq_count:c {l__tt_row_{##1}_seq} < \l__tt_num_vars_int}
                    {
                        \seq_put_left:cn {l__tt_row_{##1}_seq} {0}
                    }
            }
    }

\ExplSyntaxOff
\begin{document}

\[
\truthtable{a,b,c}{a+b;b*(-c);-(a+b)+(b*(-c))}
\]

\end{document}

enter image description here

An explanation of \mathord, \mathrel, \mathbin and similars can be found in TeX by Topic or in the TeXbook.

9
+50

Compatibility updates

  • release 1.2l (2017/07/26) removed an internal macro (\xint_gob_til_xint_relax) which I had used here. So I added the definition to make the code compile again.

  • (2015/03/07) since release 1.1 (2014/10/28) of xint the original code of this answer was broken. The overhaul of \xintNewExpr was mentioned in the user manual, but it should have been included among the list of "breaking changes" in CHANGES.html. Actually it is only a very esoteric feature of \xintNew(Bool)Expr whose change impacted the code here, I had forgotten that there existed some use of this in the world !

For people with xint < 1.1 the old code is there, but commented out.

In passing I mention that the logic operator && and || are now preferred by xint rather than & and |, but the latter are still accepted although perhaps they will be used in the future for other things: bitwise operations perhaps.


This is a (modest) alternative to the LaTeX3+lualatex approach. It uses xintexpr. It requires the user to employ \AND, \OR, \NOT in the logical expressions. Other keywords are \XOR, \ALL, \ANY.


latest update: the number of variables is anyhow limited to 9, because each expression will be converted into a macro with that many parameters. Hence the first answer with nested \xintFor, which was spelled out up to 5 variables had the potential of full generality in this context. But in my second answer (bottom of this answer) I generated the rows in a way analogous to the method I see in the LaTeX3+lualatex code, via binary expansion of the integers 0, 1 ... up to 2^n-1, with n the number of variables. This makes for a more compact coding (also less efficient, but that doesn't matter much).

This second answer additionally adds an optional parameter allowing the user to specify directly how the expressions will appear in the header row.

Also, it allows the final expression too to be ended by a ;.

And, a starred variant uses \halign rather than an array environment in order to allow pagebreaks. With 9 variables, there are 512 generated rows, hence this is necessary. The image is only the first page of a document having 12 of them.


update: use of ; (as in OP) rather than , to separate expressions.

The boolean expressions are typed in using \AND, \OR, \NOT and also there is \XOR, \ALL, \ANY (which have multiple arguments separated by ,). One can then customize in the macro how they are supposed to be typeset.

The boolean expressions are separated (as in the OP) by semi-colons: my initial answer used commas, because the \xintexpr parser handles this natively, also when commas are used elsewhere to separate arguments to function. However, for typesetting the first row, one had to hide such commas with braces for example \XOR({a,b,c}), or \XOR(a\SEP b\SEP c) with \def\SEP{,}, not very convenient. Thus, expressions in the user input are now separated using ;.

As one can see, the code here is quite short. I ran a bit out of steam at the end and possibly there could be a way to shorten it somewhat more and avoid the big \ifcase. As it stands, the code allow to construct a table with up to 5 indeterminates represented as letters (enlarging the \ifcase one may go up to 9; with 5 letters we already have 32 rows) and arbitrarily many boolean expressions separated by commas (actually as it stands the nb of expressions plus the nb of indeterminates should be kept at most 10, see the *{10}c array preamble) [I didn't have time to check if one can replace 10 by something dynamical, in that case there is no problem computing it from the two arguments to \truthtable]

truthtables

Two more truth tables:

more truth tables

\documentclass{article}
\usepackage{xintexpr}

\catcode`_ 11

\newcount \tt_varcnt

% this internal macro was suppressed in xint 1.2l (2017/07/26)
% but is used in this answer
\long\def\xint_gob_til_xint_relax #1\xint_relax {}%
\let\xint_relax\relax

% auxiliary macro to handle things separated by semi-colons
\def\truthtable_scanexprs #1;{%
    \xint_gob_til_xint_relax #1\truthtable_scan_done\xint_relax
    \odef\tt_exprlist {\tt_exprlist{#1}}%
    \truthtable_scanexprs
}%
\def\truthtable_scan_done #1\truthtable_scanexprs {}                           

\newcommand\truthtable [2]{%
% example of use :
% \truthtable{a,b,c}{a\OR b; b\AND\NOT(c)}
%
% The first argument lists the used indeterminates, they must be lowercase or
% uppercase Latin letters.
% 
% The second argument is a ; separated list of logical expressions.
  \begingroup
    \endlinechar-1 \everyeof{\noexpand }% for use of \scantokens
    % initializing
    \tt_varcnt 0
    \def\tt_exprlist {}% will be {exprA}{exprB}{exprC}...
    % scan #2 for ; separated expressions and fill \tt_exprlist:
    \truthtable_scanexprs #2;\xint_relax;%
    %
    % PREPARATION FOR DEFINING THE BOOLEAN EXPRESSIONS
% MODIFIED FOR COMPATIBILITY WITH xint 1.1 OR LATER
%%%% | and & are still accepted but the manual recommend || and &&
%%%%  (2015/03/07)
    \def\OR{||}\def\AND{&&}\def\NOT{!}% needed for \xintNewBoolExpr
% original was
    % \def\OR{|}\def\AND{&}\def\NOT{!}% needed for \xintNewBoolExpr
% END
    % the negation ! must be applied to a parenthesized expression
    % 
    % extra strange set-up before \edef and then \scantokens
    % the idea is too allow, X, O, R, A, L, L, A, N, Y as indeterminates...
    % ... and for this as we will need to make them active, thus
    %     we must first replace \XOR for example by \1.
    \def\XOR{\1}\def\ALL{\2}\def\ANY{\3}%
    \let\1\relax\let\2\relax\let\3\relax
    %
    \edef\tt_x {\tt_exprlist}% replaces \XOR by \1, \ALL by \2, \ANY by \3
    %
% ORIGINAL VERSION:
    % % we now make active the indeterminates to replace them by {$1}, {$2}, ..
    % % place holders (cf.  xintexpr manual)
    % \xintFor ##1 in {#1} \do 
    %   {\catcode`##1\active
    %    \advance\tt_varcnt 1
    %    \lccode`~=`##1\relax
    %    \lowercase{\edef~}{{$\the\tt_varcnt}}%$1, $2, $3, as in xintexpr manual
    % }%
% NEW VERSION FOR COMPATIBILITY WITH xint 1.1 OR LATER  (2015/03/07)
    % we now make active the indeterminates to replace them by {#1}, {#2}, ..
    % place holders (with a catcode 12 # as it simplifies things here, and
    % the \xintNewExpr scanner does not mind)
    \xintFor ##1 in {#1} \do% 
    % I should have defined a sub-macro for this, code would be clearer
        {\catcode`##1\active
         \advance\tt_varcnt 1
         \lccode`~=`##1\relax
         \lowercase{\edef~}{{\string####\the\tt_varcnt}}}%
% END OF MODIFICATION
    %
    % Now \scantokens to replace the now active letters by the braced
    % macro parameters (I don't recall exactly why I wanted them braced,
    % these braces will get removed during \xintNewExpr scan)
    %
    \edef\tt_x {\scantokens\expandafter{\tt_x}}%
    %
    % For use of \xintNewBoolExpr (which again does some \scantokens), 
    % the letters *must* be reset to their standard catcodes.
    %
    \xintFor ##1 in {#1} \do {\catcode`##1 11 }% 
    %
    % Definition of the boolean expressions. Using ; as separator for
    % user input was only in order to ease up
% NOTE: (2015/03/07)
% for simplicity I stick here to the originally used ; as expression separator
% on input, but xint 1.1 has macros which can be used, out of \xintexpr
% context to identify (expandably) comma separated things, even themselves
% containing commas if they are in parentheses
    % finding the correct
    % expressions for typesetting the head-row of the table, we need to
    % re-install here the , as separator, this allows to do only one
    % \xintNewBoolExpr, as it knows how to identify the various 
    % comma separated sub-expressions.
    %
    \def\1{xor}\def\2{all}\def\3{any}%
    \xintNewBoolExpr\tt_y[\tt_varcnt]{\xintListWithSep{,}{\tt_x}}%
    % 
    % CUSTOMIZE HERE:
    % For typesetting the head row, customize as desired:
    \def\OR{+}\def\AND{\cdot}\def\NOT{\mathord{\sim}}%
    \def\XOR{\mbox{\texttt{xor}}}%
    \def\ALL{\mbox{\texttt{all}}}%
    \def\ANY{\mbox{\texttt{any}}}%
    %
    % the table. Up to five variables, extensible up to use of nine variables
    \begin{array}{*{10}c}
       \xintListWithSep{&}{\xintCSVtoList{#1}}&%
                                 \xintListWithSep{&}{\tt_exprlist}\\
       \hline
       \ifcase\tt_varcnt
       \or
       \xintFor* ##1 in {01}\do
       {##1 & \xintListWithSep{&}{\xintCSVtoList{\tt_y {##1}}}\\ }
       \or
       \xintFor* ##1 in {01}\do
       {\xintFor* ##2 in {01}\do
        {##1 & ##2& 
           \xintListWithSep{&}{\xintCSVtoList{\tt_y {##1}{##2}}}\\ }}
       \or
       \xintFor* ##1 in {01}\do
       {\xintFor* ##2 in {01}\do
        {\xintFor* ##3 in {01}\do
         {##1 & ##2 & ##3& \xintListWithSep{&}%
                           {\xintCSVtoList{\tt_y {##1}{##2}{##3}}}\\ }}}
       \or
       \xintFor* ##1 in {01}\do
       {\xintFor* ##2 in {01}\do
        {\xintFor* ##3 in {01}\do
         {\xintFor* ##4 in {01}\do
          {##1 & ##2 & ##3& ##4&\xintListWithSep{&}%
                           {\xintCSVtoList{\tt_y {##1}{##2}{##3}{##4}}}\\ }}}}
       \or
       \xintFor* ##1 in {01}\do
       {\xintFor* ##2 in {01}\do
        {\xintFor* ##3 in {01}\do
         {\xintFor* ##4 in {01}\do
          {\xintFor* ##5 in {01}\do
          {##1 & ##2 & ##3& ##4& ##5&\xintListWithSep{&}%
                      {\xintCSVtoList{\tt_y {##1}{##2}{##3}{##4}{##5}}}\\ }}}}}
       \fi
       \hline 
    \end{array}
    \endgroup
}
\catcode`_ 8   

\begin{document}

\[
\truthtable{a,b,c}{a\OR b; b\AND\NOT(c); \NOT (a\OR b)\OR (b\AND\NOT(c))}
\]

\[
\truthtable{X,Y,Z}{Z\AND X \OR Y \AND X}
\]

\[
\truthtable{X,Y,Z,T}{Z\AND X \OR Y \AND X \AND T}
\]

\[
\truthtable{a, b}{\XOR (a,b)}
\]

\[
\truthtable {p, q, r, s, t}{\ANY(p,q,r,s,t); \XOR(p,q,r,s,t); \ALL(p,q,r,s,t)}
\]

\[
\truthtable{a, b, c, d}{\XOR (a,b,c,d); \XOR(a,\XOR(b,c,d)); \XOR(\XOR(a,b),\XOR(c,d))}
\]

\[
\truthtable{A,D,N}{A\AND D \OR N \AND D; A\OR D \AND N \OR D}
\]

\end{document}

Here is the code of the second answer:

\documentclass[a4paper]{article}
\usepackage{geometry}
\usepackage{xintexpr}
% in order to convert from decimal to binary using \xintDecBin
\usepackage{xintbinhex}

\catcode`_ 11
\makeatletter

% to count the number of variables:
\newcount \tt_varcnt
% The number of variables is at most 9 (leading to 512 rows...)

% to count the number of expressions (in order to choose dynamically
% a large enough number of columns for the array environment)
\newcount \tt_exprcnt

% to handle things separated by semi-colons

% we add some extra to allow empty expressions to be skipped, and also to allow
% the user to terminate the last expression by a ; 

% [if the optional parameter is used, use \space to get something empty in the
% header row cell]

% this internal macro was suppressed in xint 1.2l (2017/07/26)
% but is used in this answer
\long\def\xint_gob_til_xint_relax #1\xint_relax {}%
\let\xint_relax\relax

\def\truthtable_scanexprs #1;{%
    \if\relax\detokenize{#1}\relax\expandafter\truthtable_scan_skip\fi
    \xint_gob_til_xint_relax #1\truthtable_scan_done\xint_relax
    \odef\tt_exprlist {\tt_exprlist{#1}}%
    \advance \tt_exprcnt 1
    \truthtable_scanexprs
}%
\def\truthtable_scan_done #1\truthtable_scanexprs {}%                     
\def\truthtable_scan_skip #1\truthtable_scanexprs {\truthtable_scanexprs}%

% we now have an optional parameter to provide a custom typesetting
% of the expressions in the head row

% and we have furthermore a star variant to use \halign rather than array

\newcommand*\truthtable {%
   \begingroup
     \endlinechar-1 \everyeof{\noexpand }% for use of \scantokens
     \tt_varcnt 0
     \tt_exprcnt 0
     \def\tt_exprlist {}%
     \def\tt_headexprlist {\tt_exprlist}%
     \def\tt_usearray {1}%
     \@ifstar{\def\tt_usearray {0}\truthtable@chkopt}\truthtable@chkopt
}%

\def\truthtable@chkopt {\@ifnextchar[{\truthtable@opt}{\truthtable@}%]
                       }

\def\truthtable@opt [#1]{%
      % we first scan the optional argument for the header row.
      % it will be printed as is, only need to transform the ;'s into &'s
      \truthtable_scanexprs #1;\xint_relax;%
      \let\tt_headexprlist\tt_exprlist
      \tt_exprcnt 0
      \def\tt_exprlist {}%
      \truthtable@
      % *NO* check is done that #1 defines the same number of expressions
      % as the last mandatory argument to \truthtable !
}

\def\truthtable@ #1#2{%
    % convert the comma separated indeterminates into a list
    \oodef\tt_varlist{\xintCSVtoListNoExpand {#1}}%
    % scan #2 for ; separated expressions and fill \tt_exprlist:
    \truthtable_scanexprs #2;\xint_relax;%
    % PREPARATION FOR DEFINING THE BOOLEAN EXPRESSIONS
% MODIFIED FOR COMPATIBILITY WITH xint 1.1 OR LATER
%%%% | and & are still accepted but the manual recommend || and &&
%%%%  (2015/03/07)
    \def\OR{||}\def\AND{&&}\def\NOT{!}% needed for \xintNewBoolExpr
% original was
    % \def\OR{|}\def\AND{&}\def\NOT{!}% needed for \xintNewBoolExpr
% END
    % the negation ! must be applied to a parenthesized expression
    % 
    % extra strange set-up before \edef and then \scantokens
    % the idea is too allow, X, O, R, A, L, L, A, N, Y as indeterminates...
    % ... and for this as we will need to make them active, thus
    %     we must first replace \XOR for example by \1.
    \def\XOR{\1}\def\ALL{\2}\def\ANY{\3}%
    \let\1\relax\let\2\relax\let\3\relax
    %
    \edef\tt_x {\tt_exprlist}%  replaces \XOR by \1, \ALL by \2, \ANY by \3
    %
% ORIGINAL VERSION:
    % % we now make active the indeterminates to replace them by {$1}, {$2}, ..
    % % place holders (cf.  xintexpr manual)
    % \xintFor ##1 in {#1} \do 
    %   {\catcode`##1\active
    %    \advance\tt_varcnt 1
    %    \lccode`~=`##1\relax
    %    \lowercase{\edef~}{{$\the\tt_varcnt}}%$1, $2, $3, as in xintexpr manual
    % }%
% NEW VERSION FOR COMPATIBILITY WITH xint 1.1 OR LATER  (2015/03/07)
    % we now make active the indeterminates to replace them by {#1}, {#2}, ..
    % place holders (with a catcode 12 # as it simplifies things here, and
    % the \xintNewExpr scanner does not mind)
    \xintFor ##1 in {#1} \do% 
    % I should have defined a sub-macro for this, code would be clearer
        {\catcode`##1\active
         \advance\tt_varcnt 1
         \lccode`~=`##1\relax
         \lowercase{\edef~}{{\string####\the\tt_varcnt}}}%
% END OF MODIFICATION
    % Now the \scantokens with active letters. 
    \edef\tt_x {\scantokens\expandafter{\tt_x}}%
    %
    % For use of \xintNewBoolExpr (which again does some \scantokens), 
    % the letters *must* recover their standard catcodes
    %
    \xintFor* ##1 in \tt_varlist \do {\catcode`##1 11 }% 
    %
    % Definition of the boolean expressions. Using ; as separator for
    % user input was only in order to ease up finding the correct
    % expressions for typesetting the head-row of the table, we need to
    % re-install here the , as separator, this allows to do only one
    % \xintNewBoolExpr, it knows how to identify the various sub-expressions.
    \def\1{xor}\def\2{all}\def\3{any}%
    \xintNewBoolExpr\tt_y[\tt_varcnt]{\xintListWithSep{,}{\tt_x}}%
    % 
    % CUSTOMIZE HERE:
    % For typesetting the head row, customize as desired:
    % (*not* relevant in case of use of the optional parameter)
    \def\OR{+}\def\AND{\cdot}\def\NOT{\mathord{\sim}}%
    \def\XOR{\mbox{\texttt{xor}}}%
    \def\ALL{\mbox{\texttt{all}}}%
    \def\ANY{\mbox{\texttt{any}}}%
    %
    \if\tt_usearray 1\expandafter\truthtable_array\else
                     \expandafter\truthtable_halign
    \fi
    \endgroup
}

\def\truthtable_array {%
    % (we have the count of variables in \tt_varcnt and the count of 
    %  expressions in \tt_exprcnt)
    \begin{array}{*{\numexpr\tt_varcnt+\tt_exprcnt\relax}c}
       % generate the header-row, 
         \xintListWithSep{&}{\tt_varlist}
       &
         \xintListWithSep{&}{\tt_headexprlist}\\
       \hline
       % calculate 2^n, n is nb of variables
       \tt_varcnt \xintiiPow {2}{\tt_varcnt} 
       % generate integers from 2^n  to 2*2^n -1, then binary notation
       % will always start by a 1 which we can gobble
       \xintFor* ##1 in {\xintSeq {\tt_varcnt}{2*\tt_varcnt-1}}
       \do
       %    (the problem is that \xintDecToBin always trims leading zeros...
       %     hence we resort to this to always have a leading 1)
       {\edef\tt_temp{\expandafter\expandafter\expandafter
                      \xint_gobble_i\xintDecToBin {##1}}%
        \edef\tt_temp{\tt_temp\xintCSVtoList{\expandafter\tt_y\tt_temp}}%
        \xintListWithSep {&}{\tt_temp}\\}
       \hline 
    \end{array}
}

\newtoks\tt_toks

% The \halign variant (should *not* be put inside \[..\])
\def\truthtable_halign {%
    % This is variant using \halign to allow break accross pages.
    % I use some tricks to center it (whow! incredibly it works)
    % (I initially used a repeatable preamble, but following TeX by Topic
    %  25.5, to center the alignment, I need to insert some \tabskip
    %  at the LAST column, hence here I follow a more complicated approach
    %  constructing the preamble first.)
    %  
    % but then I don't know how to have horizontal rules limited to
    % the width covered by the actual contents... 
    %
    \tt_toks {\hfil$\mathstrut##$\hfil\tabskip 2\arraycolsep}%
    \xintiloop [{\tt_varcnt+\tt_exprcnt-1}+-1]
    \ifnum\xintiloopindex>0
      \tt_toks \expandafter{\the\tt_toks &\hfil$##$\hfil}%
    \repeat
    %\showthe\tt_toks
    \tabskip 0pt plus 1000pt minus 1000pt 
    \halign to \hsize
    {\span\the\tt_toks\tabskip 0pt plus 1000pt minus 1000pt \cr
     % first row
     \xintListWithSep{&}{\tt_varlist}&\xintListWithSep{&}{\tt_headexprlist}\cr
     % however the rule extends across the full page
     %     \hline
     \tt_varcnt \xintiiPow {2}{\tt_varcnt} 
     \xintFor* ##1 in {\xintSeq {\tt_varcnt}{2*\tt_varcnt-1}}
       \do
       {\edef\tt_temp{\expandafter\expandafter\expandafter
                      \xint_gobble_i\xintDecToBin {##1}}%
        \edef\tt_temp{\tt_temp\xintCSVtoList{\expandafter\tt_y\tt_temp}}%
        \xintListWithSep {&}{\tt_temp}\cr }%
     %      \hline 
    }%
}

\catcode`_ 8   
\makeatother

\pagestyle{empty}
\begin{document}\thispagestyle{empty}

\[
\truthtable{a,b,c}{a\OR b; b\AND\NOT(c); \NOT (a\OR b)\OR (b\AND\NOT(c));}
\]


\textbf{Exercise:~} fill in the header row:

\[% in the optional argument, \AND, \NOT, are not mandatory but optional
  % on can use whatever one wants.
\truthtable [a?b; ?\cdot\NOT(c); \NOT(a+?)?(b\AND\NOT(?));]% trailing ; allowed
            {a, b, c}
            {a\OR b; b\AND\NOT(c); \NOT (a\OR b)\OR (b\AND\NOT(c))}
\]


% maximal number of variables is 9

% \truthtable* {a, b, c}{\XOR(a, b, c)}
\bigskip
\hrule
\medskip
An example with the maximal number of variables (9, hence 512 rows) using
\verb|\halign| to allow breaking accross pages:\medskip

\truthtable* [\XOR(a,\cdots,i); ?(a,\cdots,d)\AND\NOT(?(e,\cdots,i));]
             {a, b , c, d, e, f, g, h, i}
             {\XOR(a, b , c, d, e, f, g, h, i); 
              \ALL(a,b,c,d)\AND\NOT(\ANY(e,f,g,h,i))
            }

\end{document}

truth table ...

15
  • Fantastic answer! :) Feb 27, 2014 at 9:39
  • @PauloCereda thanks :). I just now notice that the OP code needs lualatex, it was not guaranteed that \xintexpr could do about as LaTeX3+lualatex...
    – user4686
    Feb 27, 2014 at 10:04
  • @jfbu: Thank you very much for this alternative. I'll try to understand it but until then, I've a suggestion. Is it difficult to add an optional parameter which could be used to replace header boolean expressions? Something like \truthtable[f(a,b,c)]{a,b,c}{a*b+c} where a*b+c is used to compute the result but f(a,b,c) is shown in column header. This could be useful to save space and also useful to type problems where you want to hide the real function used to type truthtable. If you want I can open a new question. In any case, thanks again.
    – Ignasi
    Feb 27, 2014 at 19:03
  • @Ignasi adding such an optional parameter is not difficult, in fact that would be easiest part in the whole thing :). I will do it (I will add this in an update incorporating my earlier comment).
    – user4686
    Feb 28, 2014 at 16:44
  • @jfbu Excuse me but I've not seen your update until now. It will suit all my needs for truth tables. I've accepted Gonzalo's answer because he solved my real question, but I've also opened a bounty for your great work. May be you can consider to include it in xint. Thank you again.
    – Ignasi
    Mar 11, 2014 at 12:57
5

I am providing another answer for two reasons:

  1. since fall 2015 relase of xint a simpler approach is possible; no need to make letters active to scan for variables.

  2. This simpler approach is also more powerful: it is not needed to format the input with \AND, \OR, etc.. macros; it can be given in the syntax which understands \xintexpr. It will then be possible to both evaluate and format the original expressions with suitable symbols.

  3. questions were asked in comments to the first answer how to add more operators. It is not possible to handle binary infix logic operators which are not already known to the xintexpr syntax. But some operators can be simulated by the comparison operators. One does use \IFF etc.. notations, not because it is mandatory, but because the notation with comparison operators (on input) is non-intuitive.

See the code comments for more explanations.

(update is only about some changes of detail in implementation and code comments)

\documentclass[a4paper]{article}

\usepackage{geometry}
\usepackage{xintexpr}

\usepackage{newtxtext,newtxmath}
\usepackage[straightquotes]{newtxtt}

\usepackage{longtable, array, color, bm}% Thanks to D.C.
\setlength{\LTcapwidth}{\textwidth}

\makeatletter
% Utility macro to use directly the underlying macro foo with variables bar
% as defined earlier  by \xintdeffunc foo(bar):=...;
\newcommand*\UseUserFuncMacro [2]{\csname XINT_expr_userfunc_#1\endcsname #2,}

% Define logical values:
\xintdefvar T:= 1;
\xintdefvar F:= 0;

% We will use input with control sequences \AND, etc.. 


% The mechanism of the Truth Table necessitates that the xintexpr syntax
% has some infix binary operator pre-assigned to the given operation

% This is currently the case for AND, OR, XOR (and NOT as function).

% Currently there is no available operator for things such as 
% NOR, NAND, XNOR.

% **This will have to wait for a future package release.**

% Some operations can be simulated by comparison operators

% IFF             can be simulated by =
% IMPLIES         can be simulated by <=
% IS IMPLIED BY   can be simulated by >=
% DOES NOT IMPLY  can be simulated by >
% IS NOT IMPLIED  can be simulated by <
% IS NOT EQUIV.   can be simulated by != but same as XOR

% BUT THE PRECEDENCE LEVELS MUST BE MODIFIED. 

% This is easier than adding a new infix operator to the xintexpr syntax. 

% Changing precedences could have been a one-liner but unfortunately xintexpr
% (1.2e) has some unnecessarily hard-coded things. Thus we have to do some
% redefinitions. Adding genuine new operators rather than abusing already
% existing ones would require a few more definitions, but this is best left to
% a package upgrade.

% ----

% The approach here is to code the formulas with macros \IFF, \IMP, \ISIMP,
% \NIMP, \NISIMP. For coherence also \AND, \OR, \XOR,  will be defined, but
% direct use of the &&, 'and, ||, 'or', 'xor' is possible.

% The input expressions will be used for two things :

% 1. format the expression with suitable math mode symbols
% 2. evaluate logic expressions.

% **** Control sequences \IFF etc... in the input are only needed due to the
% fact that direct use of the ersatz <=, <, >, >= gives counterintuitive
% looking formulas on input. ****

% The \NOT has a **mandatory** parenthesized argument and \NOT(P), not(P),
% !(P) are equivalent.


% In the following it would have been more efficient to use "iiexpr",
% "\xintdefiifunc", "\xintiiLtorEq", etc ...but there is a bug in
% current xint (1.2e): the macros associated to == and <= by
% \xintiiexpr were forgotten for inclusion in the
% \xintdefiifunc/xintNewIIExpr mechanism. Hence we must use \xintexpr.

% Because they may be used in captions etc...
% ************ Although here I have used a \detokenize 
% ************ to allow for example also &&, hence it is
% ************ not so much needed to have the \AND etc robust
\DeclareRobustCommand*\AND{\string\AND\space}
\DeclareRobustCommand*\OR{\string\OR\space}
\DeclareRobustCommand*\NOT{\string\NOT\space}
\DeclareRobustCommand*\XOR{\string\XOR\space}
\DeclareRobustCommand*\IMP{\string\IMP\space}% <=
\DeclareRobustCommand*\ISIMP{\string\IMP\space}% >=
\DeclareRobustCommand*\NIMP{\string\NIMP\space}% >
\DeclareRobustCommand*\NISIMP{\string\NISIMP\space}% <
\DeclareRobustCommand*\IFF{\string\IFF\space}% =
%\DeclareRobustCommand*\NIFF{\string\NIFF\space}% =
\def\T{T}

% Note that use of \AND, \OR, \NOT, \XOR, is optional
% one can use 'and', 'or', 'xor' (quotes mandatory) and not or ! (no quotes,
% necessarily with parentheses not(P), !(Q) for example)
\newcommand{\TruthTableParseSyntaxOn}
% to be used in a group or environment
{\def\AND{&}% would prefer && and ||, but & diminishes the needed work in
            % AdjustPrecedences (which should not have been needed, but well).
 \def\OR{|}%
 \def\NOT{!}% used as function \NOT(P), parentheses mandatory
 \def\XOR{'xor'}% infix binary, there is also multi-variable function xor(..)
 \def\IMP{<=}\def\ISIMP{>=}\def\NIMP{>}\def\NISIMP{<}%
 \def\IFF{=}%
}
\newcommand{\TruthTableParseSyntaxOff}
{\edef\AND  {\expandafter\noexpand\csname AND \endcsname}%
\edef\OR    {\expandafter\noexpand\csname OR \endcsname}%
\edef\NOT   {\expandafter\noexpand\csname NOT \endcsname}%
\edef\XOR   {\expandafter\noexpand\csname XOR \endcsname}%
\edef\IMP   {\expandafter\noexpand\csname IMP \endcsname}%
\edef\ISIMP {\expandafter\noexpand\csname ISIMP \endcsname}%
\edef\NIMP  {\expandafter\noexpand\csname NIMP \endcsname}%
\edef\NISIMP{\expandafter\noexpand\csname NISIMP \endcsname}%
\edef\IFF   {\expandafter\noexpand\csname IFF \endcsname}%
}

% Let's now do the adjustment to infix operator precedences.

\catcode`_ 11
\catcode`& 11
\catcode`| 11
\catcode`< 11
\catcode`> 11
\catcode`= 11
\newcommand*\TruthTableAdjustPrecedences
{% to be used preferably in a group or environment as it impacts other
 % xintexpr contexts.
% The ! is always used with function syntax !(P), and as such has 
% automatically highest precedecence. Hence no change needed here for !.
 \let\XINT_expr_precedence_&  \xint_c_vi  % 6 : a high precedence
 \let\XINT_expr_precedence_|  \xint_c_v   % 5
 \let\XINT_expr_precedence_xor\xint_c_vi  % same as AND
 \let\XINT_expr_precedence_<= \xint_c_iv  % implies
 \let\XINT_expr_precedence_>= \xint_c_iv  % is implied by
 \let\XINT_expr_precedence_>  \xint_c_iv   % does not imply
 \let\XINT_expr_precedence_<  \xint_c_iv   % is not implied by
 \let\XINT_expr_precedence_=  \xint_c_iii  % iff : lowest precedence
 % Unfortunately the above is not enough because xintexpr currently
 % stupidly uses at some spot \xint_c_... constants rather than the
 % precedence control sequences above. Using etoolbox's \patchcmd is
 % not easily done due to special catcodes (in particular & must be
 % used with two distinct catcodes...) Must do brute force
 % redefinitions.
 \XINT_Redefine {&}{\xintAND}%
 \XINT_Redefine {|}{\xintOR}%
 \XINT_Redefine {xor}{\xintXOR}%
 \XINT_Redefine {<=}{\xintLtorEq}%
 \XINT_Redefine {>=}{\xintGtorEq}%
 \XINT_Redefine {<}{\xintLt}%
 \XINT_Redefine {>}{\xintGt}%
 \XINT_Redefine {=}{\xintEq}%
}%
\catcode`& 4
\catcode`| 12
\catcode`< 12
\catcode`> 12
\catcode`= 12

\def\XINT_Redefine #1{%
    \expandafter\XINT_Redefine_a
    \csname XINT_expr_until_#1_b\expandafter\endcsname
    \csname XINT_expr_until_#1_a\expandafter\endcsname
    \csname XINT_expr_precedence_#1\endcsname
}

\def\XINT_Redefine_a #1#2#3#4{%
  \def#1##1##2##3##4{\ifnum ##2>#3%
    \xint_afterfi {\expandafter #2\expandafter ##1%
     \romannumeral`\^^@\csname XINT_expr_op_##3\endcsname {##4}}%
   \else 
    \xint_afterfi {\expandafter ##2\expandafter ##3%
      \csname .=#4{\XINT_expr_unlock ##1}{\XINT_expr_unlock ##4}\endcsname }%
   \fi }%
}
\catcode`_ 8


% This section takes care of how the logic operators will be formatted
% to display the evaluated formula.
\catcode`: 11
% \newcommand{\TruthTableFormattingOn}
% % to be used in a group
% {%
%  \renewcommand*\xintAND[2]{##1\land ##2}%
%  \renewcommand*\xintOR[2]{##1\lor ##2}%
%  \renewcommand*\xintiiIsZero[1]{\neg ##1}%
%  \renewcommand*\xintXOR[2]{##1\mathbin{\mathrm{xor}}##2}%
%  \renewcommand*\xintLtorEq[2]{##1 \Rightarrow ##2}%
%  \renewcommand*\xintGtorEq[2]{##1 \Leftarrow ##2}%
%  \renewcommand*\xintLt[2]{##1 \nLeftarrow ##2}%
%  \renewcommand*\xintGt[2]{##1 \nRightarrow ##2}%
%  \renewcommand*\xintEq[2]{##1 \Leftrightarrow ##2}%
%  \renewcommand*\xintANDof:csv[1]{{\mathrm{all}(##1)}}% hide sub commas from \xintFor's sight
%  \renewcommand*\xintORof:csv[1] {{\mathrm{any}(##1)}}%
%  \renewcommand*\xintXORof:csv[1]{{\mathrm{xor}(##1)}}%
% }%

\newcommand{\TruthTableFormattingOnWithParens}
% to be used in a group
{% parentheses added to display precedence levels
% But we want to remove outermost parentheses.
% This will use a trick. The definition of \EncloseInParen below
% is done to 1) survive \edef, 2) compatible with full first expansion
% done by \UseUserFuncMacro, 3) allow easy removal of outermost ones if
% present.
% 
% ADDED NOTE: code in \TruthTable does not use an \edef anymore, hence it is
% not anymore needed for the stuff here to be \edef compatible.
% 
 \renewcommand*\xintAND[2]{\EncloseInParen{##1\land ##2}}%
 \renewcommand*\xintOR[2]{\EncloseInParen{##1\lor ##2}}%
 \renewcommand*\xintiiIsZero[1]{\neg ##1}%
 \renewcommand*\xintXOR[2]{\EncloseInParen{##1\mathbin{\mathrm{xor}}##2}}%
 \renewcommand*\xintLtorEq[2]{\EncloseInParen{##1 \Rightarrow ##2}}%
 \renewcommand*\xintGtorEq[2]{\EncloseInParen{##1 \Leftarrow ##2}}%
 \renewcommand*\xintLt[2]{\EncloseInParen{##1 \nLeftarrow ##2}}%
 \renewcommand*\xintGt[2]{\EncloseInParen{##1 \nRightarrow ##2}}%
 \renewcommand*\xintEq[2]{\EncloseInParen{##1 \Leftrightarrow ##2}}%
% There are braces here to hide commas from \xintFor on one occasion below
 \renewcommand*\xintANDof:csv[1]{{\mathrm{all}(##1)}}%
 \renewcommand*\xintORof:csv[1] {{\mathrm{any}(##1)}}%
 \renewcommand*\xintXORof:csv[1]{{\mathrm{xor}(##1)}}%
}%
\catcode`: 12

\newcommand*\EncloseInParen  {\relax\noexpand\EncloseInParen@i}
\newcommand*\FixParen [1]{\ifx\relax#1\expandafter\@gobbletwo\fi #1}
\newcommand*\EncloseInParen@i [1]
  {\mathopen{{\color{blue}\bm(}}#1\mathclose{{\color{blue}\bm)}}}

% unfortunately can't bolden delimiters directly with \bm
% \newcommand*\EncloseInParen@i [1]
%   {\mathopen{{\color{blue}\bm{\big(}}}#1\mathclose{{\color{blue}\bm{\big)}}}}
% unfortunately can't bolden delimiters with \bm
% \newcommand*\EncloseInParen@i [1]
%   {\mathopen{{\color{blue}\big(}}#1\mathclose{{\color{blue}\big)}}}


\newcommand*{\TruthTable@ZeroAndOneAsFandT}
{%
      \lccode`~`0 \lccode`F `F \lowercase{\def~{F}}%
      \lccode`~`1 \lccode`T `T \lowercase{\def~{T}}%
%      \catcode`0\active\catcode`1\active
% switch to math active, as anyhow in math mode, 
% and this spares always dangerous \scantokens
      \mathcode`0 "8000 \mathcode`1 "8000
}

% NOW TO OUR \TruthTable MACRO

\newcommand\TruthTable [2]{%
\begingroup\xintverbosetrue % for checking in the log what "stuff" is.
%
% #1 = variables, must be lowercase/uppercase single letters, comma
% separated. Use of T and F forbidden, must be "dummy" letters.
% #2 = comma separated list of expressions. Each expression may have
% (balanced) parentheses and commas, no need to hide the commas in braces.
%
% \fdef is defined in xint, it does less than \edef and is enough here.
%
  \fdef\TruthTable@Vars     {\xintCSVtoList{#1}}%
  \fdef\TruthTable@nbofvars {\xintNthElt{0}{\TruthTable@Vars}}%
  % We are not going to count expressions now, because they are comma
  % separated and possibly the comma is also in use inside as separator
  % for the arguments f the all, any, or xor functions. We could ask
  % the user to brace them, but let's be smarter.
%
   \TruthTableAdjustPrecedences
   \TruthTableParseSyntaxOn
% All the hard work will be done right now
       \xintdeffunc stuff(#1):=nuple(#2);%
   \TruthTableParseSyntaxOff
% Let's now count how many expressions we have:
  \fdef\TruthTable@nbofexprs 
     {\xintNthElt{0}{\xinttheexpr stuff(1..\TruthTable@nbofvars)\relax}}%
% We are now ready for the Table.
  \begin{longtable}
% LONGTABLE PREAMBLE
    {*{\TruthTable@nbofvars}{>{$}c<{$}}|*{\TruthTable@nbofexprs}{>{$}c<{$}}}
% LONGTABLE CAPTION
   \caption {\texttt{\protect\detokenize{#2}}}\\
%
% HEADER ROW WITH FORMATTED EXPRESSIONS (showing precedences with parentheses)
% 
   \xintListWithSep{&}{\TruthTable@Vars}% list of variables
   &%
      \TruthTableFormattingOnWithParens
% The #1 here isn't trimmed of spaces. But we will display in math mode.
      \edef\TruthTable@Formatted {\UseUserFuncMacro{stuff}{#1}}%
      \xintFor ##1 in \TruthTable@Formatted \do 
       {%
   %
   % a complication arise if we want to allow use of T and F in the input
   % syntax, because they have been converted to 1's and 0's. We could demand
   % use of \T and \F, but let's do our (dangerous) \scantokens tricks.
   % Update: I use rather math active characters, as we are in math mode.
   %
   % The "stuff" function will have simplified all things initially like (T
   % \AND T) etc... The only way for this to not happen would be to use \T and
   % \F with some suitable definitions in \TruthTableParseSyntaxOn. But these
   % definitions would depend on inner knowledge of \xintdeffunc/\xintNewExpr
   % functioning.
   %
          \TruthTable@ZeroAndOneAsFandT
          \FixParen ##1\xintifForLast{}{&}%
       }%
   \\
   \hline
%
% THE 2^N EVALUATION ROWS
% We first generate recursively the input truth values
% Later we will use \scantokens tricks to get the commas 
% to act as tabulations. This is faster than parsing them.
% It is not allowed to have ZERO variables ...
  \def\TruthTable@varlist {\do T\do F}%
  \def\do ##1{\noexpand\do {##1,T}\noexpand\do {##1,F}}
  \count@\numexpr\TruthTable@nbofvars\relax
    \xintloop
    \ifnum\count@>\@ne
        \edef\TruthTable@varlist{\TruthTable@varlist}%
        \advance\count@\m@ne
    \repeat
  \let\do\empty
  \edef\TruthTable@varlist{\TruthTable@varlist}%
  \xintFor* ##1 in \TruthTable@varlist \do 
  {%
   % first, the variables for this row. They are a comma separated list.
   % We could use an \xintFor to inserte tabulations, but let's play
   % with catcodes.
      \catcode`, 4 \makeatletter
      \scantokens {\csname @firstofone\endcsname {##1}}% 
       % \scantokens{##1} would not work (exercise...)
      &%
   % second the evaluated expressions.
   % We will obtain a comma separated list of 0's and 1's.
   % We must use \xinttheexpr rather than \UseUserFuncMacro
   % as only inside \xinttheexpr will T and F be interpreted as truth values.
      \fdef\TruthTable@B{\xinttheexpr stuff(##1)\relax}%
   % We now need to convert commas into tabs, could use \xintFor, but well.
      \catcode`, 4 \makeatletter
      \scantokens \expandafter{\expandafter
          \def\expandafter\TruthTable@B\expandafter{\TruthTable@B}%
          }%
% We played some tricks to display the computed 0's and 1's as T's and F's.
% We used at some pointactive characters,
% but it is also possible to do \lowercase.
      \lccode`0 `F \lccode`1 `T
      \lowercase\expandafter{\TruthTable@B}%
   \\%
  }%
  \end{longtable}
\endgroup
}

\makeatother

\begin{document}

% All used variables in second argument must be declared in first argument.

\TruthTable {P, Q}{T, F, P && Q, P||Q, P \XOR T, Q \XOR F}

\TruthTable {P, Q}{P \IMP Q, P \ISIMP Q, P \NIMP Q, P\NISIMP Q, P\IFF Q}

\TruthTable {P,Q}{P 'and' Q, P \AND Q, P\OR Q, P 'or' Q, P\XOR Q, P
  'xor' Q, !(P), not(P)}

\TruthTable {P,Q}{\NOT(P)\OR\NOT(Q), P\IFF Q, P\IMP Q, P\IFF T, Q\IFF F}%

\TruthTable {P,Q}{P\XOR Q, P\AND\NOT(Q)\OR\NOT(P)\AND Q,
                  P\XOR Q \IFF P\AND\NOT(Q)\OR\NOT(P)\AND Q}

\TruthTable {P, Q}{\NOT(P\AND Q)\IFF \NOT(P)\OR\NOT(Q)}

\TruthTable {P,Q}{P\IMP Q, \NOT(Q)\IMP\NOT(P), \NOT(P)\OR Q, \NOT(P\IMP Q), P \AND \NOT(Q)}

\TruthTable {P, Q}{P\IMP Q\IFF \NOT(Q)\IMP \NOT(P)}

\typeout{ICI}\TruthTable {P, Q}{P\IFF Q\IFF (P\IMP Q)\AND(Q\IMP P)}

\TruthTable {P,Q}{P\XOR \NOT(Q)\IFF (P\IFF Q), 
                  P\IMP Q\IFF \NOT (Q)\IMP \NOT(P)}


\TruthTable {P,Q,R}{P\AND Q\AND R, P\OR Q\AND R}

\TruthTable {P,Q,R,S}{xor(P,Q,R,S), (P \XOR Q) \XOR (R \XOR S)}

\TruthTable {P,Q,R}{(P \XOR Q) \XOR R \IFF P \XOR (Q \XOR R)}

\TruthTable {P, Q, R, S}{(P \IMP Q) \AND (Q \IMP R) \AND (R\IMP S) \AND (S \IMP P)\IMP (Q \IFF R)} 

\TruthTable {P, Q, R, S}{(P \OR Q\IMP R)&& (R\IMP S)\IMP (\NOT(S)\IMP\NOT(P))}

\listoftables
\end{document}

Output:

Blockquote

Blockquote

Blockquote

Blockquote

3
  • Nice code, but you're changing the syntax.
    – egreg
    Feb 20, 2016 at 22:04
  • @egreg do you mean compared to my other answer? yes the expressions were separated by semi-colons. Having them separated by commas makes it easier to defined in one go an xintexpr function, as it knows that. This approach can use as input directly the syntax as understood by \xintexpr. In the other answer, it was really needed to code the operators with macros. Here it is needed only because using <= for "implies" is counter-intuitive.
    – user4686
    Feb 20, 2016 at 22:20
  • @egreg I get it, I think you meant the syntax of the OP ... well yes that's true. But I think my earlier answer also did not obey the OP's input syntax. With the approach here it is actually possible to obey it exactly, simply by modifying the macros associated by xintexpr to operators +, * and -. Well, actually for - it is so deep in xintexpr perhaps I am optimistic ;-)
    – user4686
    Feb 20, 2016 at 22:33
2

Here is another solution that completes and draws a truth table, from foreach loops on different variables. The logical functions are defined from the mathematical functions of pgfmath The table is drawn with tikz

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{automata,arrows,calc,positioning,fit}

\newcommand{\fffa}[3]{
\pgfmathtruncatemacro{\ffa}{(#1 && #2) ||#3 }
\ffa
}
\begin{document}

\newcommand{\fffb}[3]{
\pgfmathtruncatemacro{\ffb}{#1 &&(#2 ||#3) }
\ffb
}


\begin{tikzpicture}[every node/.style={minimum height=1.5em}]
\node(a){a};
\node[right=2em of a](b){b};
\node[right=2em of b](c){c};
\node[right=2em of c](d){$a\cdot b + c$};
\node[right=2em of d](e){$a \cdot ( b + c)$};
\node[fit =(a) (b) (c) (d) (e)] (l1){};
\draw(l1.south east) --(l1.south west);
\foreach \aa in{0,1}{
    \foreach \bb in{0,1}{
        \foreach \cc in{0,1}{
         \node[below =1em of a](a){\aa};
         \node[below =1em of b](b){\bb};
         \node[below =1em of c](c){\cc};
         \node[below =1em of d](d){\fffa{\aa}{\bb}{\cc}};                
         \node[below =1em of e](e){\fffb{\aa}{\bb}{\cc}};
         \node[fit =(a) (b) (c) (d) (e)] (l2){};
         \draw(l1.east|-l2.south west) -| (l1.north west);
        }
    }
}
\draw (l1.north west) -|(l2.south east-|l1.north east);

\def\lastx{a}
\foreach \nn [remember=\nn as \lastx]in  {b,c,d,e}{
\draw ($0.5*(\lastx)+0.5*(\nn)$) coordinate (xx)  ( xx|-l1.north) -- ( xx|-l2.south) ;
\def\mm{\nn}
}

\end{tikzpicture}


\end{document}

enter image description here

You must log in to answer this question.

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