# How to change operator symbols in truth table

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

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:
{
\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

\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:
}

{
\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}}
\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}

-
\mathord{\sim}? –  Gonzalo Medina Feb 26 at 18:59
@GonzaloMedina. It works! What does \mathord do? It's the first time I see this command. –  Ignasi Feb 26 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). –  Gonzalo Medina Feb 26 at 19:11

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:
{
\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

\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:
}

{
\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}}
\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}


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

-

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]

Two more truth tables:

\documentclass{article}
\usepackage{xintexpr}

\catcode_ 11

\newcount \tt_varcnt

% 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]{%
\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
\def\OR{|}\def\AND{&}\def\NOT{!}% needed for \xintNewBoolExpr
% 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...
\def\XOR{\1}\def\ALL{\2}\def\ANY{\3}%
\let\1\relax\let\2\relax\let\3\relax
%
\edef\tt_x {\tt_exprlist}%
%
% expands \OR, \AND, \NOT, \XOR, \ALL, \ANY, to things not containing
% letters. Hence no requirement that the indeterminates should not be
% X, O, R, A, L, L, A, N, Y.
%
% 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
\lccode~=##1\relax
\lowercase{\edef~}{{$\the\tt_varcnt}}%$1, $2,$3, as in xintexpr manual
}%
%
% 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 {#1} \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:
\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

\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}}%
\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_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;%
\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
\def\OR{|}\def\AND{&}\def\NOT{!}% needed for \xintNewBoolExpr
% 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...
\def\XOR{\1}\def\ALL{\2}\def\ANY{\3}%
\let\1\relax\let\2\relax\let\3\relax
%
\edef\tt_x {\tt_exprlist}%
%
% expands \OR, \AND, \NOT, \XOR, \ALL, \ANY, to things not containing
% letters. Hence no requirement that the indeterminates should not be
% X, O, R, A, L, L, A, N, Y.
%
% we now make active the indeterminates to replace them by {$1}, {$2}, ..
% place holders (cf.  xintexpr manual)
\xintFor* ##1 in \tt_varlist \do
{\catcode##1\active
\lccode~=##1
\lowercase{\edef~}{{$\the\tt_varcnt}}%$1, $2,$3, as in xintexpr manual
}%
%
% 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}
\xintListWithSep{&}{\tt_varlist}
&
\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
% 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}


-
Fantastic answer! :) –  Paulo Cereda Feb 27 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... –  jfbu Feb 27 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 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). –  jfbu Feb 28 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 at 12:57