# Macro for Automating Truth Tables

I used this truth table when writing a proof.
Unfortunately I made a tiny typo; The first time around, I misplaced an additional ʹ symbol, which just happened to make the output exactly wrong. It ended up taking me forever to understand why nothing after that seemed to add up the way I knew it should.

It led me to wonder. Is it possible to build some kind of macro that will allow me to generate accurate truth tables according to a given input? In other words: I'd like it to work something like:

\truthtable{A, B, ( A \oplus B )', (A) \oplus (B')}

As opposed to having to draw the entire thing manually, potentially making errors. How would you even begin to program something like this in LaTeX?

Here's the full manual MWE for the table pictured above:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\documentclass[border=10pt]{standalone}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%hdashline
\usepackage{array}
\usepackage{arydshln}
\setlength\dashlinedash{0.2pt}
\setlength\dashlinegap{1.5pt}
\setlength\arrayrulewidth{0.3pt}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{document}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{table}[htbp!]
\centering
\caption{}
\label{tab}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{tabular}{@{}cccc@{}}
\toprule%%–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
$A$ & $B$ & $(A \oplus B)'$ & $(A) \oplus (B')$ \\
\midrule%%–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
0 & 0 & 1 & 1 \\ \hdashline%%··········································
0 & 1 & 0 & 0 \\ \hdashline%%··········································
1 & 0 & 0 & 0 \\ \hdashline%%··········································
1 & 1 & 1 & 1 \\ \bottomrule%––––––––––––––––––––––––––––––––––––––––––
\end{tabular}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\end{table}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

• Do you want the 0 and 1 to fill in somehow automatically based on the column headers or yould you be satisfied with manually entering thouse entries? – leandriis Aug 27 at 20:20
• Well, I already have manually entered those values. But yes, the idea is to give it the column headers, and have it calculate the output accordingly, to preclude any inaccuracies. Plus it would just be super quick and convenient. Eventually I would like to make a whole collection of similar tools to help make latex a breeze – voices Aug 27 at 20:31
• This looks like something that would be fairly easy to do with LuaTeX. There are some python truth table calculators on github that would be easy to adapt to Lua I should think. e.g. gist.github.com/a-andreyev/8b72ed4f7913da7b35313044185f6e11 – Alan Munn Aug 27 at 20:45
• One way to do this would be to use the collcell package and define a column type that compute the given function. The you would leave the output cells empty and the value would be computed based on the input values. This would require that you define macros that compute the output values based on the input columns (which may be more work than manually verifying a table). But, if you need many of the types of tables often then it may be worthwhile. – Peter Grill Aug 28 at 0:08
• Yes, you can also program a Mars rover in TeX (among other things) What is the most bizarre thing you have seen done with TeX but just because you can, doesn't mean you should. :) – Alan Munn Aug 28 at 0:25

This is something quickly written. Does not steer the Mars rover, in fact it is just a one-liner. If you make your code copyable I will add all the table options, if needed. (In fact, I made zero efforts to make the output "pretty", nor to suppress spaces or empty lines.) Also one can use a loop to create all the rows. I added explanations to the output.

\documentclass{article}
\usepackage{pgf}
\newcounter{step}
\newcommand{\myrow}[2]{ #1 & #2 &
\pgfmathparse{not(int(mod(#1+#2,2)))}\pgfmathresult &
\pgfmathparse{int(mod(#1+not(#2),2))}\pgfmathresult\\
}
\setcounter{step}{0}
\def\tabcontent{\stepcounter{step}\ifnum\value{step}<5
\pgfmathtruncatemacro{\myA}{(\value{step}-1)/2}%
\pgfmathtruncatemacro{\myB}{mod(\value{step}-1,2)}%
\edef\temp{\noexpand\myrow{\myA}{\myB}}\temp%
\tabcontent\fi}
\begin{document}
In the first example (Table~\ref{tab:First}), explicit macros
\verb|\myrow{A}{B}| are used for each row. The \verb|\myrow| macro takes two
arguments, which are $A$ and $B$ in your application.

\begin{table}[!h]
\centering
\begin{tabular}{c@{}cccc@{}}
~$A$~ & ~$B$~ & $(A \oplus B)'$ & $(A) \oplus (B')$ \\
\hline
\myrow{0}{0}
\myrow{0}{1}
\myrow{1}{0}
\myrow{1}{1}
\end{tabular}
\caption{First example.}
\label{tab:First}
\end{table}

The important point is that the other entries of the remaining columns can be
computed with \texttt{pgf} via \verb|\pgfmathparse{not(int(mod(#1+#2,2)))}| and
\verb|\pgfmathparse{int(mod(#1+not(#2),2))}|, respectively. I strongly suspect
that other packages like \texttt{xint} allow you to do similar things. However,
I am most familiar with \texttt{pgf}.

In the second example (Table~\ref{tab:Second}), a loop produces the content
of the table. Since the \& character is notoriously nasty, this loop is realized
as a recursive macro. Alternatives to this recursion include the \verb|\gappto|
macro that comes with the \texttt{etoolbox} package.
\begin{table}[!h]
\centering
\begin{tabular}{c@{}cccc@{}}
~$A$~ & ~$B$~ & $(A \oplus B)'$ & $(A) \oplus (B')$ \\
\hline
\tabcontent
\end{tabular}
\caption{Second example.}
\label{tab:Second}
\end{table}
\end{document}


• "This is something ultra quickly written." No worries, I appreciate your input. "..in fact it is just a one-liner." That might actually be my favourite kind. "If you make your code copyable.." Can you not copy it? What do you need me to do? – voices Aug 28 at 18:17
• So, in the meantime, what have you done here exactly? Can we walk through it a little? I guess you imported a library (\usepackage{pgf}) to access the \pgfmathparse and \pgfmathresult commands which seem to calculate and print the correct values based on their input. Then you declare it as a function by wrapping it between \newcommand{\myrow}[2]{ #1 & #2 & and }; {\myrow} gives it a name, [2] declares the input/argument count where #1 and #2 correspond with the argument values and & is the alignment operator. Am I close? – voices Aug 28 at 18:48
• @tjt263 That’s basically correct. I am traveling right now, so I won’t dare to edit the answer on my cell phone. \myrow creates a row of the table, and, as you say, it takes two arguments, the values of A and B which it Prints and uses for the entries of the other two columns. The main point, though, is that pgf has the functions you need built in, as this illustrates. (There are other packages that do that, too.) – Schrödinger's cat Aug 28 at 21:03
• @tjt263 Copyable means that there should not be any nonstandard characters in, as there are e.g. after \toprule in the comments. Also make sure the code can be compiled. – Schrödinger's cat Aug 28 at 21:32

The not so short answer, where the code turned out to be longer than in my head. The code may look a bit cumbersome, but I did not optimise it in any way. I just wanted to show a solution where the input is interpreted and a truthtable is generated e.g. (A\oplus B)' as A XOR B and the result is negated. The code does not except every possible inputs. For example, there must only be one logical operator: \land, \lor and \oplus. So (A\land B) \land \neg (A\land B) is not a valid input. There can be multiple operators like \neg and '.

It should be possible to extend the code to overcome this limitation.

\documentclass{scrartcl}

\usepackage{xparse}
\usepackage{array}
\usepackage{booktabs}

\ExplSyntaxOn

\tl_new:N \l__truthtable_op_tmp_tl
\tl_new:N \l__truthtable_logical_tmp_tl
\tl_new:N \l__truthtable_expression_tmp_tl
\seq_new:N \l__truthtable_logical_tmp_seq

\tl_new:N \l__truthtable_expression_left_tl
\tl_new:N \l__truthtable_expression_right_tl
\tl_new:N \l__truthtable_expression_out_tl

\seq_new:N \l__truthtable_op_seq

\seq_new:N \l__truthtable_vara_logical_seq
\seq_new:N \l__truthtable_varb_logical_seq
\seq_new:N \l__truthtable_op_logical_seq

\seq_new:N \l__truthtable_expression_seq
\seq_new:N \l__truthtable_expression_split_seq

\int_new:N \l__truthtable_expression_int
\int_new:N \g__truthtable_tmp_int

\bool_new:N \l__truthtable_vara_bool
\bool_new:N \l__truthtable_varb_bool
\bool_new:N \l__truthtable_result_bool

\NewDocumentCommand{\truthtable}{ m m m }
{
\group_begin:
\seq_set_from_clist:Nn \l__truthtable_expression_seq {#3}
\int_set:Nn \l__truthtable_expression_int { \seq_count:N \l__truthtable_expression_seq }

\seq_map_function:NN  \l__truthtable_expression_seq \truthtable_parse:n
\truthtable_truthtable:NNnn \l__truthtable_expression_seq \l__truthtable_expression_int {#1} {#2}
\group_end:
}

\cs_new_protected:Npn \truthtable_truthtable_begin:N #1
{
\tabular{ *{ \int_eval:n { #1 + 2 } }{ >{$}c<{$} } }
\toprule
}

\cs_new_protected:Npn \truthtable_truthtable_end:
{
\\ \bottomrule
\endtabular
}

\cs_new_protected:Npn \truthtable_truthtable:NNnn #1 #2 #3 #4
{
\truthtable_truthtable_begin:N #2
\truthtable_truthtable_content:N   #2
\truthtable_truthtable_end:
}

\cs_new_protected:Npn \truthtable_truthtable_header:NNnn #1 #2 #3 #4
{
#3 & #4
\int_compare:nNnF {#2} = { 0 }
{  & \seq_use:Nn #1 { & }  }
\\ \midrule
}

\cs_new_protected:Npn \truthtable_truthtable_content:N #1
{
\int_gzero:N \g__truthtable_tmp_int
\int_step_inline:nnn { 0 } { 1 }
{
\int_step_inline:nnn { 0 } { 1 }
{
##1 & ####1

\truthtable_evaluate:Nnn #1 {##1} {####1}

\__truthtable_newline:N \g__truthtable_tmp_int
}
}
}

\cs_new_protected:Npn \__truthtable_newline:N #1
{
\int_gincr:N #1
\int_compare:nNnF {#1} = { 4 }
{ \\ }
}

\cs_new_protected:Npn \truthtable_parse:n #1
{
\tl_map_function:nN {#1} \__truthtable_get_operator:n

\__truthtable_split_at_operator:n {#1}

\truthtable_if_odd:VTF \l__truthtable_expression_left_tl
{  \truthtable_odd:NN  \l__truthtable_expression_left_tl \l__truthtable_expression_right_tl  }
{  \truthtable_even:NN \l__truthtable_expression_left_tl \l__truthtable_expression_right_tl  }

}

\cs_new_protected:Npn \__truthtable_get_operator:n #1
{
\str_case:nnT {#1}
{
{ \oplus } { \seq_put_right:Nn \l__truthtable_op_seq { xor } }
{ \lor   } { \seq_put_right:Nn \l__truthtable_op_seq { or  } }
{ \land  } { \seq_put_right:Nn \l__truthtable_op_seq { and } }
}
{  \tl_map_break:n { \tl_set:Nn \l__truthtable_op_tmp_tl {#1} }  }
}

\cs_new_protected:Npn \__truthtable_split_at_operator:n #1
{
\exp_args:NNV
\seq_set_split:Nnn \l__truthtable_expression_split_seq \l__truthtable_op_tmp_tl {#1}

\tl_set:Nx \l__truthtable_expression_left_tl  { \seq_item:Nn \l__truthtable_expression_split_seq { 1 } }
\tl_set:Nx \l__truthtable_expression_right_tl { \seq_item:Nn \l__truthtable_expression_split_seq { 2 } }
}

\cs_new_protected:Npn \truthtable_odd:NN #1 #2
{
\tl_clear:N \l__truthtable_expression_out_tl
\tl_reverse:N #2
\truthtable_odd_aux:NNn #1 \l__truthtable_expression_tmp_tl { ( }
\truthtable_odd_aux:NNn #2 \l__truthtable_expression_tmp_tl { ) }
\tl_reverse:N #2

\truthtable_get_logicals:NN \l__truthtable_expression_out_tl \l__truthtable_op_logical_seq
\truthtable_get_logicals:NN #1 \l__truthtable_vara_logical_seq
\truthtable_get_logicals:NN #2 \l__truthtable_varb_logical_seq
}

\cs_new_protected:Npn \truthtable_odd_aux:NNn #1 #2 #3
{
\tl_set_eq:NN #2 #1

\tl_map_inline:Nn #1
{
\str_if_eq:nnTF {##1} {#3}
{
\tl_set:Nx #2 { \tl_tail:N #2 }
\tl_map_break:
}
{
\tl_put_right:Nx \l__truthtable_expression_out_tl { \tl_head:N #2 }
\tl_set:Nx #2 { \tl_tail:N #2 }
}
}
\tl_set_eq:NN #1 #2
}

\cs_new_protected:Npn \truthtable_even:NN #1 #2
{
\truthtable_get_logicals:NN #1 \l__truthtable_vara_logical_seq
\truthtable_get_logicals:NN #2 \l__truthtable_varb_logical_seq
\seq_put_right:Nn \l__truthtable_op_logical_seq { }
}

\cs_new_protected:Npn \truthtable_get_logicals:NN #1 #2
{
\tl_clear:N \l__truthtable_logical_tmp_tl
\tl_map_inline:Nn #1
{
\str_case:nn {##1}
{
{ '    } { \truthtable_add_to_tl:Nnn \l__truthtable_logical_tmp_tl { not } { , } }
{ \neg } { \truthtable_add_to_tl:Nnn \l__truthtable_logical_tmp_tl { not } { , } }
}
}
\seq_put_right:NV #2 \l__truthtable_logical_tmp_tl
}

{
\tl_if_empty:NTF #1
{  \tl_set:Nn       #1 {  #2}  }
{  \tl_put_right:Nn #1 {#3#2}  }
}

\cs_new_protected:Npn \truthtable_evaluate:Nnn #1 #2 #3
{
\int_step_inline:nnn { 1 } {#1}
{
&
\truthtable_set_bool:Nn \l__truthtable_vara_bool {#2}
\truthtable_set_bool:Nn \l__truthtable_varb_bool {#3}
\truthtable_eval_logical:NNn \l__truthtable_vara_logical_seq \l__truthtable_vara_bool {##1}
\truthtable_eval_logical:NNn \l__truthtable_varb_logical_seq \l__truthtable_varb_bool {##1}

\truthtable_eval_operator:NNn \l__truthtable_vara_bool \l__truthtable_varb_bool {##1}
\truthtable_print_result:NNn \l__truthtable_op_logical_seq \l__truthtable_result_bool {##1}
}
}

\cs_new_protected:Npn \truthtable_eval_operator:NNn #1 #2 #3
{
\str_case_e:nn { \seq_item:Nn \l__truthtable_op_seq {#3} }
{
{ xor } { \truthtable_xor:NN #1 #2 }
{ or  } { \truthtable_or:NN  #1 #2 }
{ and } { \truthtable_and:NN #1 #2 }
}
}

\cs_new_protected:Npn \truthtable_eval_logical:NNn #1 #2 #3
{
\exp_args:NNx
\seq_set_from_clist:Nn \l__truthtable_logical_tmp_seq { \seq_item:Nn #1 {#3} }

\seq_map_inline:Nn \l__truthtable_logical_tmp_seq
{
\str_case:nn {##1}
{
{ not } { \truthtable_not:N #2 }
}
}
}

\cs_new_protected:Npn \truthtable_print_result:NNn #1 #2 #3
{
\truthtable_eval_logical:NNn #1 #2 {#3}

\bool_if:NTF #2
{ 1 }
{ 0 }
}

\cs_new_protected:Npn \truthtable_set_bool:Nn #1 #2
{
\int_case:nn {#2}
{
{ 0 } { \bool_gset_false:N #1 }
{ 1 } { \bool_gset_true:N  #1 }
}
}

\cs_new_protected:Npn \truthtable_xor:NN #1 #2
{
\bool_xor:nnTF {#1} {#2}
{ \bool_set_true:N  \l__truthtable_result_bool }
{ \bool_set_false:N \l__truthtable_result_bool }
}

\cs_new_protected:Npn \truthtable_not:N #1
{
\bool_set_inverse:N #1
}

\cs_new_protected:Npn \truthtable_or:NN #1 #2
{
\bool_lazy_or:nnTF {#1} {#2}
{ \bool_set_true:N  \l__truthtable_result_bool }
{ \bool_set_false:N \l__truthtable_result_bool }
}

\cs_new_protected:Npn \truthtable_and:NN #1 #2
{
\bool_lazy_and:nnTF {#1} {#2}
{ \bool_set_true:N  \l__truthtable_result_bool }
{ \bool_set_false:N \l__truthtable_result_bool }
}

\prg_new_conditional:Npnn \truthtable_if_odd:n #1 { T, F, TF }
{
\regex_count:nnN { $$} {#1} \l_tmpa_int \regex_count:nnN {$$ } {#1} \l_tmpb_int

\int_if_odd:nTF { \l_tmpa_int + \l_tmpb_int }
{  \prg_return_true:   }
{  \prg_return_false:  }
}
\prg_generate_conditional_variant:Nnn \truthtable_if_odd:n { V } { TF }

\ExplSyntaxOff

\begin{document}
\truthtable{A}{B}
{
(A\oplus B)',
(A)\oplus(B'),
A\oplus B,
A\oplus B',
\neg((A)\oplus (B)'),
A\lor B,
A\lor B',
A\land B
}
\end{document}


giving